From 69b61d12449c15bfbe8928b35428ff10842444d8 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Tue, 3 Oct 2023 14:29:33 -0500 Subject: [PATCH] Rename device to camera --- __tests__/path.test.ts | 64 +++++++------- src/app/(static)/grid/page.tsx | 20 ++--- .../[photoId]/layout.tsx | 43 ++++++---- .../{[device] => [camera]}/[photoId]/page.tsx | 0 .../shot-on/[camera]/[photoId]/share/page.tsx | 36 ++++++++ .../{[device] => [camera]}/image/route.tsx | 14 +-- src/app/(static)/shot-on/[camera]/page.tsx | 65 ++++++++++++++ .../(static)/shot-on/[camera]/share/page.tsx | 70 +++++++++++++++ .../shot-on/[device]/[photoId]/share/page.tsx | 34 -------- src/app/(static)/shot-on/[device]/page.tsx | 65 -------------- .../(static)/shot-on/[device]/share/page.tsx | 74 ---------------- src/cache/index.ts | 20 ++--- src/camera/CameraHeader.tsx | 28 ++++++ .../CameraOGTile.tsx} | 20 ++--- src/camera/CameraShareModal.tsx | 23 +++++ .../PhotoCamera.tsx} | 20 ++--- src/camera/index.ts | 31 +++++++ src/camera/meta.ts | 35 ++++++++ src/device/DeviceHeader.tsx | 28 ------ src/device/DeviceShareModal.tsx | 23 ----- src/device/index.ts | 40 --------- src/device/meta.ts | 26 ------ src/photo/PhotoDetailPage.tsx | 20 ++--- src/photo/PhotoGrid.tsx | 8 +- src/photo/PhotoLarge.tsx | 23 ++--- src/photo/PhotoLink.tsx | 8 +- src/photo/PhotoLinks.tsx | 16 ++-- src/photo/PhotoShareModal.tsx | 10 +-- src/photo/PhotoSmall.tsx | 8 +- ...geResponse.tsx => CameraImageResponse.tsx} | 10 +-- src/photo/index.ts | 5 +- src/services/postgres.ts | 24 +++--- src/site/paths.ts | 86 +++++++++---------- src/tag/index.ts | 4 +- 34 files changed, 504 insertions(+), 497 deletions(-) rename src/app/(static)/shot-on/{[device] => [camera]}/[photoId]/layout.tsx (59%) rename src/app/(static)/shot-on/{[device] => [camera]}/[photoId]/page.tsx (100%) create mode 100644 src/app/(static)/shot-on/[camera]/[photoId]/share/page.tsx rename src/app/(static)/shot-on/{[device] => [camera]}/image/route.tsx (73%) create mode 100644 src/app/(static)/shot-on/[camera]/page.tsx create mode 100644 src/app/(static)/shot-on/[camera]/share/page.tsx delete mode 100644 src/app/(static)/shot-on/[device]/[photoId]/share/page.tsx delete mode 100644 src/app/(static)/shot-on/[device]/page.tsx delete mode 100644 src/app/(static)/shot-on/[device]/share/page.tsx create mode 100644 src/camera/CameraHeader.tsx rename src/{device/DeviceOGTile.tsx => camera/CameraOGTile.tsx} (56%) create mode 100644 src/camera/CameraShareModal.tsx rename src/{device/PhotoDevice.tsx => camera/PhotoCamera.tsx} (68%) create mode 100644 src/camera/index.ts create mode 100644 src/camera/meta.ts delete mode 100644 src/device/DeviceHeader.tsx delete mode 100644 src/device/DeviceShareModal.tsx delete mode 100644 src/device/index.ts delete mode 100644 src/device/meta.ts rename src/photo/image-response/{DeviceImageResponse.tsx => CameraImageResponse.tsx} (81%) diff --git a/__tests__/path.test.ts b/__tests__/path.test.ts index bd9d54b0..eb4828bf 100644 --- a/__tests__/path.test.ts +++ b/__tests__/path.test.ts @@ -2,10 +2,10 @@ import '@testing-library/jest-dom'; import { getEscapePath, getPathComponents, - isPathDevice, - isPathDevicePhoto, - isPathDevicePhotoShare, - isPathDeviceShare, + isPathCamera, + isPathCameraPhoto, + isPathCameraPhotoShare, + isPathCameraShare, isPathPhoto, isPathPhotoShare, isPathTag, @@ -13,12 +13,12 @@ import { isPathTagPhotoShare, isPathTagShare, } from '@/site/paths'; -import { getMakeModelFromDeviceString } from '@/device'; +import { getMakeModelFromCameraString } from '@/camera'; const PHOTO_ID = 'UsKSGcbt'; const TAG = 'tag-name'; -const DEVICE = 'fujifilm-x-t1'; -const DEVICE_OBJECT = getMakeModelFromDeviceString(DEVICE); +const CAMERA = 'fujifilm-x-t1'; +const CAMERA_OBJECT = getMakeModelFromCameraString(CAMERA); const SHARE = 'share'; const PATH_ROOT = '/'; @@ -33,10 +33,10 @@ const PATH_TAG_SHARE = `${PATH_TAG}/${SHARE}`; const PATH_TAG_PHOTO = `${PATH_TAG}/${PHOTO_ID}`; const PATH_TAG_PHOTO_SHARE = `${PATH_TAG_PHOTO}/${SHARE}`; -const PATH_DEVICE = `/shot-on/${DEVICE}`; -const PATH_DEVICE_SHARE = `${PATH_DEVICE}/${SHARE}`; -const PATH_DEVICE_PHOTO = `${PATH_DEVICE}/${PHOTO_ID}`; -const PATH_DEVICE_PHOTO_SHARE = `${PATH_DEVICE_PHOTO}/${SHARE}`; +const PATH_CAMERA = `/shot-on/${CAMERA}`; +const PATH_CAMERA_SHARE = `${PATH_CAMERA}/${SHARE}`; +const PATH_CAMERA_PHOTO = `${PATH_CAMERA}/${PHOTO_ID}`; +const PATH_CAMERA_PHOTO_SHARE = `${PATH_CAMERA_PHOTO}/${SHARE}`; describe('Paths', () => { it('can be classified', () => { @@ -47,10 +47,10 @@ describe('Paths', () => { expect(isPathTagShare(PATH_TAG_SHARE)).toBe(true); expect(isPathTagPhoto(PATH_TAG_PHOTO)).toBe(true); expect(isPathTagPhotoShare(PATH_TAG_PHOTO_SHARE)).toBe(true); - expect(isPathDevice(PATH_DEVICE)).toBe(true); - expect(isPathDeviceShare(PATH_DEVICE_SHARE)).toBe(true); - expect(isPathDevicePhoto(PATH_DEVICE_PHOTO)).toBe(true); - expect(isPathDevicePhotoShare(PATH_DEVICE_PHOTO_SHARE)).toBe(true); + expect(isPathCamera(PATH_CAMERA)).toBe(true); + expect(isPathCameraShare(PATH_CAMERA_SHARE)).toBe(true); + expect(isPathCameraPhoto(PATH_CAMERA_PHOTO)).toBe(true); + expect(isPathCameraPhotoShare(PATH_CAMERA_PHOTO_SHARE)).toBe(true); // Negative expect(isPathPhoto(PATH_TAG_PHOTO_SHARE)).toBe(false); expect(isPathPhotoShare(PATH_TAG_PHOTO)).toBe(false); @@ -58,10 +58,10 @@ describe('Paths', () => { expect(isPathTagShare(PATH_TAG)).toBe(false); expect(isPathTagPhoto(PATH_PHOTO_SHARE)).toBe(false); expect(isPathTagPhotoShare(PATH_PHOTO)).toBe(false); - expect(isPathDevice(PATH_TAG_SHARE)).toBe(false); - expect(isPathDeviceShare(PATH_TAG)).toBe(false); - expect(isPathDevicePhoto(PATH_PHOTO_SHARE)).toBe(false); - expect(isPathDevicePhotoShare(PATH_PHOTO)).toBe(false); + expect(isPathCamera(PATH_TAG_SHARE)).toBe(false); + expect(isPathCameraShare(PATH_TAG)).toBe(false); + expect(isPathCameraPhoto(PATH_PHOTO_SHARE)).toBe(false); + expect(isPathCameraPhotoShare(PATH_PHOTO)).toBe(false); }); it('can be parsed', () => { expect(getPathComponents(PATH_ROOT)).toEqual({}); @@ -85,19 +85,19 @@ describe('Paths', () => { photoId: PHOTO_ID, tag: TAG, }); - expect(getPathComponents(PATH_DEVICE)).toEqual({ - device: DEVICE_OBJECT, + expect(getPathComponents(PATH_CAMERA)).toEqual({ + camera: CAMERA_OBJECT, }); - expect(getPathComponents(PATH_DEVICE_SHARE)).toEqual({ - device: DEVICE_OBJECT, + expect(getPathComponents(PATH_CAMERA_SHARE)).toEqual({ + camera: CAMERA_OBJECT, }); - expect(getPathComponents(PATH_DEVICE_PHOTO)).toEqual({ + expect(getPathComponents(PATH_CAMERA_PHOTO)).toEqual({ photoId: PHOTO_ID, - device: DEVICE_OBJECT, + camera: CAMERA_OBJECT, }); - expect(getPathComponents(PATH_DEVICE_PHOTO_SHARE)).toEqual({ + expect(getPathComponents(PATH_CAMERA_PHOTO_SHARE)).toEqual({ photoId: PHOTO_ID, - device: DEVICE_OBJECT, + camera: CAMERA_OBJECT, }); }); it('can be escaped', () => { @@ -113,10 +113,10 @@ describe('Paths', () => { expect(getEscapePath(PATH_TAG_SHARE)).toEqual(PATH_TAG); expect(getEscapePath(PATH_TAG_PHOTO)).toEqual(PATH_TAG); expect(getEscapePath(PATH_TAG_PHOTO_SHARE)).toEqual(PATH_TAG_PHOTO); - // Device views - expect(getEscapePath(PATH_DEVICE)).toEqual(PATH_GRID); - expect(getEscapePath(PATH_DEVICE_SHARE)).toEqual(PATH_DEVICE); - expect(getEscapePath(PATH_DEVICE_PHOTO)).toEqual(PATH_DEVICE); - expect(getEscapePath(PATH_DEVICE_PHOTO_SHARE)).toEqual(PATH_DEVICE_PHOTO); + // Camera views + expect(getEscapePath(PATH_CAMERA)).toEqual(PATH_GRID); + expect(getEscapePath(PATH_CAMERA_SHARE)).toEqual(PATH_CAMERA); + expect(getEscapePath(PATH_CAMERA_PHOTO)).toEqual(PATH_CAMERA); + expect(getEscapePath(PATH_CAMERA_PHOTO_SHARE)).toEqual(PATH_CAMERA_PHOTO); }); }); diff --git a/src/app/(static)/grid/page.tsx b/src/app/(static)/grid/page.tsx index da635ae6..a01890da 100644 --- a/src/app/(static)/grid/page.tsx +++ b/src/app/(static)/grid/page.tsx @@ -1,14 +1,14 @@ import { getPhotosCached, getPhotosCountCached, - getUniqueDevicesCached, + getUniqueCamerasCached, getUniqueTagsCached, } from '@/cache'; import HeaderList from '@/components/HeaderList'; import MorePhotos from '@/components/MorePhotos'; import SiteGrid from '@/components/SiteGrid'; import { generateOgImageMetaForPhotos, getPhotosLimitForQuery } from '@/photo'; -import PhotoDevice from '@/device/PhotoDevice'; +import PhotoCamera from '@/camera/PhotoCamera'; import PhotoGrid from '@/photo/PhotoGrid'; import PhotosEmptyState from '@/photo/PhotosEmptyState'; import { MAX_PHOTOS_TO_SHOW_HOME } from '@/photo/image-response'; @@ -36,12 +36,12 @@ export default async function GridPage({ photos, count, tags, - devices, + cameras, ] = await Promise.all([ getPhotosCached({ limit }), getPhotosCountCached(), getUniqueTagsCached(), - getUniqueDevicesCached(), + getUniqueCamerasCached(), ]); const showMorePhotos = count > photos.length; @@ -65,13 +65,13 @@ export default async function GridPage({ showIcon={false} />)} />} - {devices.length > 0 && 0 && } - items={devices.map(({ deviceKey, device }) => - + )} diff --git a/src/app/(static)/shot-on/[device]/[photoId]/layout.tsx b/src/app/(static)/shot-on/[camera]/[photoId]/layout.tsx similarity index 59% rename from src/app/(static)/shot-on/[device]/[photoId]/layout.tsx rename to src/app/(static)/shot-on/[camera]/[photoId]/layout.tsx index 71d03728..90fbd171 100644 --- a/src/app/(static)/shot-on/[device]/[photoId]/layout.tsx +++ b/src/app/(static)/shot-on/[camera]/[photoId]/layout.tsx @@ -11,17 +11,21 @@ import { } from '@/site/paths'; import PhotoDetailPage from '@/photo/PhotoDetailPage'; import { getPhotoCached, getPhotosCached } from '@/cache'; -import { getPhotos, getUniqueDevices } from '@/services/postgres'; -import { deviceFromPhoto } from '@/device'; +import { getPhotos, getUniqueCameras } from '@/services/postgres'; +import { cameraFromPhoto } from '@/camera'; + +interface PhotoCameraProps { + params: { photoId: string, camera: string } +} export async function generateStaticParams() { - const params: { params: { photoId: string, device: string }}[] = []; + const params: PhotoCameraProps[] = []; - const devices = await getUniqueDevices(); - devices.forEach(async ({ deviceKey, device }) => { - const photos = await getPhotos({ device }); + const cameras = await getUniqueCameras(); + cameras.forEach(async ({ cameraKey, camera }) => { + const photos = await getPhotos({ camera }); params.push(...photos.map(photo => ({ - params: { photoId: photo.id, device: deviceKey }, + params: { photoId: photo.id, camera: cameraKey }, }))); }); @@ -29,10 +33,8 @@ export async function generateStaticParams() { } export async function generateMetadata({ - params: { photoId }, -}: { - params: { photoId: string, device: string } -}): Promise { + params: { photoId, camera }, +}: PhotoCameraProps): Promise { const photo = await getPhotoCached(photoId); if (!photo) { return {}; } @@ -40,7 +42,11 @@ export async function generateMetadata({ const title = titleForPhoto(photo); const description = descriptionForPhoto(photo); const images = absolutePathForPhotoImage(photo); - const url = absolutePathForPhoto(photo, undefined, deviceFromPhoto(photo)); + const url = absolutePathForPhoto( + photo, + undefined, + cameraFromPhoto(photo, camera), + ); return { title, @@ -60,27 +66,26 @@ export async function generateMetadata({ }; } -export default async function PhotoDevicePage({ - params: { photoId }, +export default async function PhotoCameraPage({ + params: { photoId, camera: cameraProp }, children, -}: { - params: { photoId: string, tag: string } +}: PhotoCameraProps & { children: React.ReactNode }) { const photo = await getPhotoCached(photoId); if (!photo) { redirect(PATH_ROOT); } - const device = deviceFromPhoto(photo); + const camera = cameraFromPhoto(photo, cameraProp); - const photos = await getPhotosCached({ device }); + const photos = await getPhotosCached({ camera }); return <> {children} ; } diff --git a/src/app/(static)/shot-on/[device]/[photoId]/page.tsx b/src/app/(static)/shot-on/[camera]/[photoId]/page.tsx similarity index 100% rename from src/app/(static)/shot-on/[device]/[photoId]/page.tsx rename to src/app/(static)/shot-on/[camera]/[photoId]/page.tsx diff --git a/src/app/(static)/shot-on/[camera]/[photoId]/share/page.tsx b/src/app/(static)/shot-on/[camera]/[photoId]/share/page.tsx new file mode 100644 index 00000000..0b3f76d8 --- /dev/null +++ b/src/app/(static)/shot-on/[camera]/[photoId]/share/page.tsx @@ -0,0 +1,36 @@ +import { getPhotoCached } from '@/cache'; +import { cameraFromPhoto } from '@/camera'; +import PhotoShareModal from '@/photo/PhotoShareModal'; +import { getPhotos, getUniqueCameras } from '@/services/postgres'; +import { PATH_ROOT } from '@/site/paths'; +import { redirect } from 'next/navigation'; + +interface PhotoCameraParams { + params: { photoId: string, camera: string } +} + +export async function generateStaticParams() { + const params: PhotoCameraParams[] = []; + + const cameras = await getUniqueCameras(); + cameras.forEach(async ({ cameraKey, camera }) => { + const photos = await getPhotos({ camera }); + params.push(...photos.map(photo => ({ + params: { photoId: photo.id, camera: cameraKey }, + }))); + }); + + return params; +} + +export default async function Share({ + params: { photoId, camera: cameraProp }, +}: PhotoCameraParams) { + const photo = await getPhotoCached(photoId); + + if (!photo) { return redirect(PATH_ROOT); } + + const camera = cameraFromPhoto(photo, cameraProp); + + return ; +} diff --git a/src/app/(static)/shot-on/[device]/image/route.tsx b/src/app/(static)/shot-on/[camera]/image/route.tsx similarity index 73% rename from src/app/(static)/shot-on/[device]/image/route.tsx rename to src/app/(static)/shot-on/[camera]/image/route.tsx index f04ec80f..be0111fa 100644 --- a/src/app/(static)/shot-on/[device]/image/route.tsx +++ b/src/app/(static)/shot-on/[camera]/image/route.tsx @@ -1,11 +1,11 @@ import { auth } from '@/auth'; import { getImageCacheHeadersForAuth, getPhotosCached } from '@/cache'; -import { getMakeModelFromDeviceString } from '@/device'; +import { getMakeModelFromCameraString } from '@/camera'; import { IMAGE_OG_SMALL_SIZE, MAX_PHOTOS_TO_SHOW_PER_TAG, } from '@/photo/image-response'; -import DeviceImageResponse from '@/photo/image-response/DeviceImageResponse'; +import CameraImageResponse from '@/photo/image-response/CameraImageResponse'; import { getIBMPlexMonoMedium } from '@/site/font'; import { ImageResponse } from 'next/server'; @@ -13,9 +13,9 @@ export const runtime = 'edge'; export async function GET( _: Request, - context: { params: { device: string } }, + context: { params: { camera: string } }, ) { - const device = getMakeModelFromDeviceString(context.params.device); + const camera = getMakeModelFromCameraString(context.params.camera); const [ photos, @@ -24,7 +24,7 @@ export async function GET( ] = await Promise.all([ getPhotosCached({ limit: MAX_PHOTOS_TO_SHOW_PER_TAG, - device, + camera: camera, }), getIBMPlexMonoMedium(), getImageCacheHeadersForAuth(await auth()), @@ -33,8 +33,8 @@ export async function GET( const { width, height } = IMAGE_OG_SMALL_SIZE; return new ImageResponse( - ({ + params: { camera: cameraKey }, + })); +} + +export async function generateMetadata({ + params, +}: CameraProps): Promise { + const camera = getMakeModelFromCameraString(params.camera); + const photos = await getPhotosCached({ camera }); + + const { + url, + title, + description, + images, + } = generateMetaForCamera(camera, photos); + + return { + title, + openGraph: { + title, + description, + images, + url, + }, + twitter: { + images, + description, + card: 'summary_large_image', + }, + description, + }; +} + +export default async function CameraPage({ params }:CameraProps) { + const camera = getMakeModelFromCameraString(params.camera); + + const photos = await getPhotosCached({ camera }); + + return ( + + + + } + /> + ); +} diff --git a/src/app/(static)/shot-on/[camera]/share/page.tsx b/src/app/(static)/shot-on/[camera]/share/page.tsx new file mode 100644 index 00000000..8511e357 --- /dev/null +++ b/src/app/(static)/shot-on/[camera]/share/page.tsx @@ -0,0 +1,70 @@ +import { getPhotosCached } from '@/cache'; +import SiteGrid from '@/components/SiteGrid'; +import { cameraFromPhoto, getMakeModelFromCameraString } from '@/camera'; +import CameraHeader from '@/camera/CameraHeader'; +import CameraShareModal from '@/camera/CameraShareModal'; +import { generateMetaForCamera } from '@/camera/meta'; +import PhotoGrid from '@/photo/PhotoGrid'; +import { getUniqueCameras } from '@/services/postgres'; +import { Metadata } from 'next'; + +interface CameraProps { + params: { camera: string } +} + +export async function generateStaticParams() { + const camera = await getUniqueCameras(); + return camera.map(({ cameraKey }): CameraProps => ({ + params: { camera: cameraKey }, + })); +} + +export async function generateMetadata({ + params, +}: CameraProps): Promise { + const camera = getMakeModelFromCameraString(params.camera); + + const photos = await getPhotosCached({ camera }); + + const { + url, + title, + description, + images, + } = generateMetaForCamera(camera, photos); + + return { + title, + openGraph: { + title, + description, + images, + url, + }, + twitter: { + images, + description, + card: 'summary_large_image', + }, + description, + }; +} + +export default async function Share({ params }: CameraProps) { + const cameraFromParams = getMakeModelFromCameraString(params.camera); + + const photos = await getPhotosCached({ camera: cameraFromParams }); + + const camera = cameraFromPhoto(photos[0], cameraFromParams); + + return <> + + + + + } + /> + ; +} diff --git a/src/app/(static)/shot-on/[device]/[photoId]/share/page.tsx b/src/app/(static)/shot-on/[device]/[photoId]/share/page.tsx deleted file mode 100644 index 1bab0fc6..00000000 --- a/src/app/(static)/shot-on/[device]/[photoId]/share/page.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { getPhotoCached } from '@/cache'; -import { deviceFromPhoto } from '@/device'; -import PhotoShareModal from '@/photo/PhotoShareModal'; -import { getPhotos, getUniqueDevices } from '@/services/postgres'; -import { PATH_ROOT } from '@/site/paths'; -import { redirect } from 'next/navigation'; - -export async function generateStaticParams() { - const params: { params: { photoId: string, device: string }}[] = []; - - const devices = await getUniqueDevices(); - devices.forEach(async ({ deviceKey, device }) => { - const photos = await getPhotos({ device }); - params.push(...photos.map(photo => ({ - params: { photoId: photo.id, device: deviceKey }, - }))); - }); - - return params; -} - -export default async function Share({ - params: { photoId }, -}: { - params: { photoId: string } -}) { - const photo = await getPhotoCached(photoId); - - if (!photo) { return redirect(PATH_ROOT); } - - const device = deviceFromPhoto(photo); - - return ; -} diff --git a/src/app/(static)/shot-on/[device]/page.tsx b/src/app/(static)/shot-on/[device]/page.tsx deleted file mode 100644 index 795f87a0..00000000 --- a/src/app/(static)/shot-on/[device]/page.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { getPhotosCached } from '@/cache'; -import SiteGrid from '@/components/SiteGrid'; -import DeviceHeader from '@/device/DeviceHeader'; -import { getMakeModelFromDeviceString } from '@/device'; -import PhotoGrid from '@/photo/PhotoGrid'; -import { getUniqueDevices } from '@/services/postgres'; -import { Metadata } from 'next'; -import { generateMetaForDevice } from '@/device/meta'; - -interface DeviceProps { - params: { device: string } -} - -export async function generateStaticParams() { - const devices = await getUniqueDevices(); - return devices.map(({ deviceKey }): DeviceProps => ({ - params: { device: deviceKey }, - })); -} - -export async function generateMetadata({ - params, -}: DeviceProps): Promise { - const device = getMakeModelFromDeviceString(params.device); - const photos = await getPhotosCached({ device }); - - const { - url, - title, - description, - images, - } = generateMetaForDevice(device, photos); - - return { - title, - openGraph: { - title, - description, - images, - url, - }, - twitter: { - images, - description, - card: 'summary_large_image', - }, - description, - }; -} - -export default async function DevicePage({ params }:DeviceProps) { - const device = getMakeModelFromDeviceString(params.device); - - const photos = await getPhotosCached({ device }); - - return ( - - - - } - /> - ); -} diff --git a/src/app/(static)/shot-on/[device]/share/page.tsx b/src/app/(static)/shot-on/[device]/share/page.tsx deleted file mode 100644 index 745fab85..00000000 --- a/src/app/(static)/shot-on/[device]/share/page.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { getPhotosCached } from '@/cache'; -import SiteGrid from '@/components/SiteGrid'; -import { deviceFromPhoto, getMakeModelFromDeviceString } from '@/device'; -import DeviceHeader from '@/device/DeviceHeader'; -import DeviceShareModal from '@/device/DeviceShareModal'; -import { generateMetaForDevice } from '@/device/meta'; -import PhotoGrid from '@/photo/PhotoGrid'; -import { getUniqueDevices } from '@/services/postgres'; -import { Metadata } from 'next'; - -interface DeviceProps { - params: { device: string } -} - -export async function generateStaticParams() { - const devices = await getUniqueDevices(); - return devices.map(({ deviceKey }): DeviceProps => ({ - params: { device: deviceKey }, - })); -} - -export async function generateMetadata({ - params, -}: DeviceProps): Promise { - const device = getMakeModelFromDeviceString(params.device); - - const photos = await getPhotosCached({ device }); - - const { - url, - title, - description, - images, - } = generateMetaForDevice(device, photos); - - return { - title, - openGraph: { - title, - description, - images, - url, - }, - twitter: { - images, - description, - card: 'summary_large_image', - }, - description, - }; -} - -export default async function Share({ - params, -}: { - params: { device: string } -}) { - const deviceFromParams = getMakeModelFromDeviceString(params.device); - - const photos = await getPhotosCached({ device: deviceFromParams }); - - const device = deviceFromPhoto(photos[0]) ?? deviceFromParams; - - return <> - - - - - } - /> - ; -} diff --git a/src/cache/index.ts b/src/cache/index.ts index f0949630..1b6988b8 100644 --- a/src/cache/index.ts +++ b/src/cache/index.ts @@ -5,7 +5,7 @@ import { getPhotos, getPhotosCount, getPhotosCountIncludingHidden, - getUniqueDevices, + getUniqueCameras, getUniqueTags, } from '@/services/postgres'; import { parseCachedPhotosDates, parseCachedPhotoDates } from '@/photo'; @@ -15,7 +15,7 @@ import { AuthSession } from 'next-auth'; const TAG_PHOTOS = 'photos'; const TAG_PHOTOS_COUNT = 'photos-count'; const TAG_TAGS = 'tags'; -const TAG_DEVICES = 'devices'; +const TAG_CAMERAS = 'cameras'; const TAG_BLOB = 'blob'; // eslint-disable-next-line max-len @@ -40,7 +40,7 @@ const getPhotosCacheTagForKey = ( return value ? `${key}-${value.toISOString()}` : null; } // Complex keys - case 'device': { + case 'camera': { const value = options[key]; return value ? `${key}-${value.make}-${value.model}` : null; } @@ -66,8 +66,8 @@ export const revalidatePhotosTag = () => export const revalidateTagsTag = () => revalidateTag(TAG_TAGS); -export const revalidateDevicesTag = () => - revalidateTag(TAG_DEVICES); +export const revalidateCamerasTag = () => + revalidateTag(TAG_CAMERAS); export const revalidateBlobTag = () => revalidateTag(TAG_BLOB); @@ -80,7 +80,7 @@ export const revalidatePhotosAndBlobTag = () => { export const revalidateAllTags = () => { revalidatePhotosTag(); revalidateTagsTag(); - revalidateDevicesTag(); + revalidateCamerasTag(); revalidateBlobTag(); }; @@ -125,11 +125,11 @@ export const getUniqueTagsCached: typeof getUniqueTags = (...args) => } )(); -export const getUniqueDevicesCached: typeof getUniqueDevices = (...args) => +export const getUniqueCamerasCached: typeof getUniqueCameras = (...args) => unstable_cache( - () => getUniqueDevices(...args), - [TAG_PHOTOS, TAG_DEVICES], { - tags: [TAG_PHOTOS, TAG_DEVICES], + () => getUniqueCameras(...args), + [TAG_PHOTOS, TAG_CAMERAS], { + tags: [TAG_PHOTOS, TAG_CAMERAS], } )(); diff --git a/src/camera/CameraHeader.tsx b/src/camera/CameraHeader.tsx new file mode 100644 index 00000000..c95df7b3 --- /dev/null +++ b/src/camera/CameraHeader.tsx @@ -0,0 +1,28 @@ +import { Photo } from '@/photo'; +import { pathForCameraShare } from '@/site/paths'; +import PhotoHeader from '@/photo/PhotoHeader'; +import { Camera, cameraFromPhoto } from '.'; +import PhotoCamera from './PhotoCamera'; +import { descriptionForCameraPhotos } from './meta'; + +export default function CameraHeader({ + camera: cameraProp, + photos, + selectedPhoto, +}: { + camera: Camera + photos: Photo[] + selectedPhoto?: Photo +}) { + const camera = cameraFromPhoto(photos[0], cameraProp); + return ( + } + entityVerb="Photo" + entityDescription={descriptionForCameraPhotos(photos)} + photos={photos} + selectedPhoto={selectedPhoto} + sharePath={pathForCameraShare(camera)} + /> + ); +} diff --git a/src/device/DeviceOGTile.tsx b/src/camera/CameraOGTile.tsx similarity index 56% rename from src/device/DeviceOGTile.tsx rename to src/camera/CameraOGTile.tsx index e68dcbe2..48f350f9 100644 --- a/src/device/DeviceOGTile.tsx +++ b/src/camera/CameraOGTile.tsx @@ -1,13 +1,13 @@ import { Photo } from '@/photo'; -import { absolutePathForDeviceImage, pathForDevice } from '@/site/paths'; +import { absolutePathForCameraImage, pathForCamera } from '@/site/paths'; import OGTile from '@/components/OGTile'; -import { Device, titleForDevice } from '.'; -import { descriptionForDevicePhotos } from './meta'; +import { Camera, titleForCamera } from '.'; +import { descriptionForCameraPhotos } from './meta'; export type OGLoadingState = 'unloaded' | 'loading' | 'loaded' | 'failed'; -export default function DeviceOGTile({ - device, +export default function CameraOGTile({ + camera, photos, loadingState: loadingStateExternal, riseOnHover, @@ -15,7 +15,7 @@ export default function DeviceOGTile({ onFail, retryTime, }: { - device: Device + camera: Camera photos: Photo[] loadingState?: OGLoadingState onLoad?: () => void @@ -25,10 +25,10 @@ export default function DeviceOGTile({ }) { return ( + + + ); +}; diff --git a/src/device/PhotoDevice.tsx b/src/camera/PhotoCamera.tsx similarity index 68% rename from src/device/PhotoDevice.tsx rename to src/camera/PhotoCamera.tsx index aea0f961..296df7ed 100644 --- a/src/device/PhotoDevice.tsx +++ b/src/camera/PhotoCamera.tsx @@ -1,22 +1,22 @@ import { AiFillApple } from 'react-icons/ai'; import { cc } from '@/utility/css'; import Link from 'next/link'; -import { pathForDevice } from '@/site/paths'; +import { pathForCamera } from '@/site/paths'; import { IoMdCamera } from 'react-icons/io'; -import { Device } from '.'; +import { Camera } from '.'; -export default function PhotoDevice({ - device, +export default function PhotoCamera({ + camera, showIcon = true, hideApple = true, }: { - device: Device + camera: Camera showIcon?: boolean hideApple?: boolean }) { return (   } - {!(hideApple && device.make?.toLowerCase() === 'apple') && + {!(hideApple && camera.make?.toLowerCase() === 'apple') && <> - {device.make?.toLowerCase() === 'apple' + {camera.make?.toLowerCase() === 'apple' ? - : device.make} + : camera.make}   } - {device.model} + {camera.model} ); } diff --git a/src/camera/index.ts b/src/camera/index.ts new file mode 100644 index 00000000..5f609c28 --- /dev/null +++ b/src/camera/index.ts @@ -0,0 +1,31 @@ +import { Photo } from '@/photo'; +import { parameterize } from '@/utility/string'; + +const CAMERA_PLACEHOLDER: Camera = { make: 'Camera', model: 'Model' }; + +export type Camera = { + make: string + model: string +}; + +export const createCameraKey = (make: string, model: string) => + parameterize(`${make}-${model}`); + +// Assumes no makes ('Fujifilm,' 'Apple,' 'Canon', etc.) have dashes +export const getMakeModelFromCameraString = (camera: string): Camera => { + const [make, model] = camera.toLowerCase().split(/[-| ](.*)/s); + return { make, model }; +}; + +export const cameraFromPhoto = ( + photo: Photo | undefined, + fallback?: Camera | string, +): Camera => + photo?.make && photo?.model + ? { make: photo.make, model: photo.model } + : typeof fallback === 'string' + ? getMakeModelFromCameraString(fallback) + : fallback ?? CAMERA_PLACEHOLDER; + +export const formatCameraText = ({ make, model }: Camera) => + `${make} ${model}`; diff --git a/src/camera/meta.ts b/src/camera/meta.ts new file mode 100644 index 00000000..6abe65a4 --- /dev/null +++ b/src/camera/meta.ts @@ -0,0 +1,35 @@ +import { Photo, descriptionForPhotoSet, photoQuantityText } from '@/photo'; +import { Camera, cameraFromPhoto, formatCameraText } from '.'; +import { + absolutePathForCamera, + absolutePathForCameraImage, +} from '@/site/paths'; + +// Meta functions moved to separate file to avoid +// dependencies (camelcase-keys) found in photo/index.ts +// which cause Jest to crash + +export const titleForCamera = ( + camera: Camera, + photos: Photo[], +) => [ + 'Shot on', + formatCameraText(cameraFromPhoto(photos[0], camera)), + photoQuantityText(photos), +].join(' '); + +export const descriptionForCameraPhotos = ( + photos: Photo[], + dateBased?: boolean, +) => + descriptionForPhotoSet(photos, 'camera', dateBased); + +export const generateMetaForCamera = ( + camera: Camera, + photos: Photo[] +) => ({ + url: absolutePathForCamera(camera), + title: titleForCamera(camera, photos), + description: descriptionForCameraPhotos(photos, true), + images: absolutePathForCameraImage(camera), +}); diff --git a/src/device/DeviceHeader.tsx b/src/device/DeviceHeader.tsx deleted file mode 100644 index f11c21c4..00000000 --- a/src/device/DeviceHeader.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Photo } from '@/photo'; -import { pathForDeviceShare } from '@/site/paths'; -import PhotoHeader from '@/photo/PhotoHeader'; -import { Device, formatDevice } from '.'; -import PhotoDevice from './PhotoDevice'; -import { descriptionForDevicePhotos } from './meta'; - -export default function DeviceHeader({ - device: deviceFromProps, - photos, - selectedPhoto, -}: { - device: Device - photos: Photo[] - selectedPhoto?: Photo -}) { - const device = formatDevice(deviceFromProps, photos[0]); - return ( - } - entityVerb="Device" - entityDescription={descriptionForDevicePhotos(photos)} - photos={photos} - selectedPhoto={selectedPhoto} - sharePath={pathForDeviceShare(device)} - /> - ); -} diff --git a/src/device/DeviceShareModal.tsx b/src/device/DeviceShareModal.tsx deleted file mode 100644 index 5a0b415c..00000000 --- a/src/device/DeviceShareModal.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { absolutePathForDevice, pathForDevice } from '@/site/paths'; -import { Photo } from '../photo'; -import ShareModal from '@/components/ShareModal'; -import DeviceOGTile from './DeviceOGTile'; -import { Device } from '.'; - -export default function DeviceShareModal({ - device, - photos, -}: { - device: Device - photos: Photo[] -}) { - return ( - - - - ); -}; diff --git a/src/device/index.ts b/src/device/index.ts deleted file mode 100644 index 43dd424a..00000000 --- a/src/device/index.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Photo } from '@/photo'; -import { capitalizeWords, parameterize } from '@/utility/string'; - -export type Device = { - make: string - model: string -}; - -export const createDeviceKey = (make: string, model: string) => - parameterize(`${make}-${model}`); - -export const titleForDevice = ( - { make, model }: Device, - photos:Photo[], -) => [ - 'Shot on', - deviceTextFromPhoto(photos[0]) ?? capitalizeWords(`${make} ${model}`), -].join(' '); - -// Assumes no device makes ('Fujifilm,' 'Apple,' 'Canon', etc.) -// will have dashes in them -export const getMakeModelFromDeviceString = (device: string): Device => { - const [make, model] = device.toLowerCase().split(/[-| ](.*)/s); - return { make, model }; -}; - -// Used to harvest original make/model with proper spaces/hyphens -export const deviceTextFromPhoto = (photo?: Photo) => photo - ? `${photo.make} ${photo.model}` - : undefined; - -export const deviceFromPhoto = (photo?: Photo): Device | undefined => - photo?.make && photo?.model - ? { make: photo.make, model: photo.model } - : undefined; - -export const formatDevice = (device: Device, photo?: Photo): Device => - photo?.make && photo?.model - ? { make: photo.make, model: photo.model } - : device; diff --git a/src/device/meta.ts b/src/device/meta.ts deleted file mode 100644 index 9102108e..00000000 --- a/src/device/meta.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Photo, descriptionForPhotoSet } from '@/photo'; -import { Device, titleForDevice } from '.'; -import { - absolutePathForDevice, - absolutePathForDeviceImage, -} from '@/site/paths'; - -// Meta functions moved to separate file to avoid -// dependencies (camelcase-keys) found in photo/index.ts -// that cause Jest to crash - -export const descriptionForDevicePhotos = ( - photos: Photo[], - dateBased?: boolean, -) => - descriptionForPhotoSet(photos, 'device', dateBased); - -export const generateMetaForDevice = ( - device: Device, - photos: Photo[] -) => ({ - url: absolutePathForDevice(device), - title: titleForDevice(device, photos), - description: descriptionForDevicePhotos(photos, true), - images: absolutePathForDeviceImage(device), -}); diff --git a/src/photo/PhotoDetailPage.tsx b/src/photo/PhotoDetailPage.tsx index 750061aa..f724e655 100644 --- a/src/photo/PhotoDetailPage.tsx +++ b/src/photo/PhotoDetailPage.tsx @@ -6,21 +6,21 @@ import PhotoGrid from './PhotoGrid'; import { cc } from '@/utility/css'; import PhotoLinks from './PhotoLinks'; import TagHeader from '@/tag/TagHeader'; -import { Device } from '@/device'; -import DeviceHeader from '@/device/DeviceHeader'; +import { Camera } from '@/camera'; +import CameraHeader from '@/camera/CameraHeader'; export default function PhotoDetailPage({ photo, photos, photosGrid, tag, - device, + camera, }: { photo: Photo photos: Photo[] photosGrid?: Photo[] tag?: string - device?: Device + camera?: Camera }) { return (
@@ -35,13 +35,13 @@ export default function PhotoDetailPage({ selectedPhoto={photo} />} />} - {device && + {camera && } @@ -56,9 +56,9 @@ export default function PhotoDetailPage({ tag={tag} priority prefetchShare - shareDevice={device !== undefined} + shareCamera={camera !== undefined} shouldScrollOnShare={false} - showDevice={false} + showCamera={!camera} />, ]} /> @@ -79,7 +79,7 @@ export default function PhotoDetailPage({ photo, photos, tag, - device, + camera, }} />
} /> diff --git a/src/photo/PhotoGrid.tsx b/src/photo/PhotoGrid.tsx index f94d9eaa..df74603c 100644 --- a/src/photo/PhotoGrid.tsx +++ b/src/photo/PhotoGrid.tsx @@ -2,13 +2,13 @@ import { Photo } from '.'; import PhotoSmall from './PhotoSmall'; import { cc } from '@/utility/css'; import AnimateItems from '@/components/AnimateItems'; -import { Device } from '@/device'; +import { Camera } from '@/camera'; export default function PhotoGrid({ photos, selectedPhoto, tag, - device, + camera, fast, animate = true, animateOnFirstLoadOnly, @@ -17,7 +17,7 @@ export default function PhotoGrid({ photos: Photo[] selectedPhoto?: Photo tag?: string - device?: Device + camera?: Camera fast?: boolean animate?: boolean animateOnFirstLoadOnly?: boolean @@ -41,7 +41,7 @@ export default function PhotoGrid({ key={photo.id} photo={photo} tag={tag} - device={device} + camera={camera} selected={photo.id === selectedPhoto?.id} />)} /> diff --git a/src/photo/PhotoLarge.tsx b/src/photo/PhotoLarge.tsx index abaa8958..6c07194e 100644 --- a/src/photo/PhotoLarge.tsx +++ b/src/photo/PhotoLarge.tsx @@ -6,29 +6,30 @@ import Link from 'next/link'; import { pathForPhoto, pathForPhotoShare } from '@/site/paths'; import PhotoTags from '@/tag/PhotoTags'; import ShareButton from '@/components/ShareButton'; -import PhotoDevice from '../device/PhotoDevice'; -import { deviceFromPhoto } from '@/device'; +import PhotoCamera from '../camera/PhotoCamera'; +import { Camera, cameraFromPhoto } from '@/camera'; export default function PhotoLarge({ photo, tag, priority, prefetchShare, - shareDevice, shouldScrollOnShare, - showDevice = true, + showCamera = true, + shareCamera, }: { photo: Photo tag?: string + camera?: Camera priority?: boolean prefetchShare?: boolean - shareDevice?: boolean shouldScrollOnShare?: boolean - showDevice?: boolean + showCamera?: boolean + shareCamera?: boolean }) { const tagsToShow = photo.tags.filter(t => t !== tag); - const device = deviceFromPhoto(photo); + const camera = cameraFromPhoto(photo); const renderMiniGrid = (children: JSX.Element) =>
0 && }
- {showDevice && device && - } @@ -114,7 +115,7 @@ export default function PhotoLarge({ path={pathForPhotoShare( photo, tag, - shareDevice ? device : undefined, + shareCamera ? camera : undefined, )} prefetch={prefetchShare} shouldScroll={shouldScrollOnShare} diff --git a/src/photo/PhotoLink.tsx b/src/photo/PhotoLink.tsx index ad2984b0..d2cd318b 100644 --- a/src/photo/PhotoLink.tsx +++ b/src/photo/PhotoLink.tsx @@ -6,19 +6,19 @@ import Link from 'next/link'; import { AnimationConfig } from '../components/AnimateItems'; import { useAppState } from '@/state'; import { pathForPhoto } from '@/site/paths'; -import { Device } from '@/device'; +import { Camera } from '@/camera'; export default function PhotoLink({ photo, tag, - device, + camera, prefetch, nextPhotoAnimation, children, }: { photo?: Photo tag?: string - device?: Device + camera?: Camera prefetch?: boolean nextPhotoAnimation?: AnimationConfig children: ReactNode @@ -28,7 +28,7 @@ export default function PhotoLink({ return ( photo ? { if (nextPhotoAnimation) { diff --git a/src/photo/PhotoLinks.tsx b/src/photo/PhotoLinks.tsx index fc62df2d..0943f336 100644 --- a/src/photo/PhotoLinks.tsx +++ b/src/photo/PhotoLinks.tsx @@ -7,7 +7,7 @@ import { useRouter } from 'next/navigation'; import { pathForPhoto } from '@/site/paths'; import { useAppState } from '@/state'; import { AnimationConfig } from '@/components/AnimateItems'; -import { Device } from '@/device'; +import { Camera } from '@/camera'; const LISTENER_KEYUP = 'keyup'; @@ -18,12 +18,12 @@ export default function PhotoLinks({ photo, photos, tag, - device, + camera, }: { photo: Photo photos: Photo[] tag?: string - device?: Device + camera?: Camera }) { const router = useRouter(); @@ -40,7 +40,7 @@ export default function PhotoLinks({ if (previousPhoto) { setNextPhotoAnimation?.(ANIMATION_RIGHT); router.push( - pathForPhoto(previousPhoto, tag, device), + pathForPhoto(previousPhoto, tag, camera), { scroll: false }, ); } @@ -50,7 +50,7 @@ export default function PhotoLinks({ if (nextPhoto) { setNextPhotoAnimation?.(ANIMATION_LEFT); router.push( - pathForPhoto(nextPhoto, tag, device), + pathForPhoto(nextPhoto, tag, camera), { scroll: false }, ); } @@ -65,7 +65,7 @@ export default function PhotoLinks({ previousPhoto, nextPhoto, tag, - device, + camera, ]); return ( @@ -74,7 +74,7 @@ export default function PhotoLinks({ photo={previousPhoto} nextPhotoAnimation={ANIMATION_RIGHT} tag={tag} - device={device} + camera={camera} prefetch > PREV @@ -83,7 +83,7 @@ export default function PhotoLinks({ photo={nextPhoto} nextPhotoAnimation={ANIMATION_LEFT} tag={tag} - device={device} + camera={camera} prefetch > NEXT diff --git a/src/photo/PhotoShareModal.tsx b/src/photo/PhotoShareModal.tsx index 41004f79..90a92d18 100644 --- a/src/photo/PhotoShareModal.tsx +++ b/src/photo/PhotoShareModal.tsx @@ -2,22 +2,22 @@ import PhotoOGTile from '@/photo/PhotoOGTile'; import { absolutePathForPhoto, pathForPhoto } from '@/site/paths'; import { Photo } from '.'; import ShareModal from '@/components/ShareModal'; -import { Device } from '@/device'; +import { Camera } from '@/camera'; export default function PhotoShareModal({ photo, tag, - device, + camera, }: { photo: Photo tag?: string - device?: Device + camera?: Camera }) { return ( diff --git a/src/photo/PhotoSmall.tsx b/src/photo/PhotoSmall.tsx index 6b5b44e4..be23c302 100644 --- a/src/photo/PhotoSmall.tsx +++ b/src/photo/PhotoSmall.tsx @@ -3,22 +3,22 @@ import ImageSmall from '@/components/ImageSmall'; import Link from 'next/link'; import { cc } from '@/utility/css'; import { pathForPhoto } from '@/site/paths'; -import { Device } from '@/device'; +import { Camera } from '@/camera'; export default function PhotoSmall({ photo, tag, - device, + camera, selected, }: { photo: Photo tag?: string - device?: Device + camera?: Camera selected?: boolean }) { return ( export const titleForPhoto = (photo: Photo) => photo.title || 'Untitled'; -export const labelForPhotos = (photos: Photo[]) => +const labelForPhotos = (photos: Photo[]) => photos.length === 1 ? 'Photo' : 'Photos'; +export const photoQuantityText = (photos: Photo[]) => + `(${photos.length} ${labelForPhotos(photos)})`; + export const descriptionForPhotoSet = ( photos:Photo[], descriptor: string, diff --git a/src/services/postgres.ts b/src/services/postgres.ts index 35d5c9b1..3c33a745 100644 --- a/src/services/postgres.ts +++ b/src/services/postgres.ts @@ -6,7 +6,7 @@ import { parsePhotoFromDb, Photo, } from '@/photo'; -import { Device, createDeviceKey } from '@/device'; +import { Camera, createCameraKey } from '@/camera'; import { parameterize } from '@/utility/string'; const PHOTO_DEFAULT_LIMIT = 100; @@ -190,7 +190,7 @@ const sqlGetPhotosByTag = ( LIMIT ${limit} OFFSET ${offset} `; -const sqlGetPhotosByDevice = async ( +const sqlGetPhotosByCamera = async ( limit = PHOTO_DEFAULT_LIMIT, make: string, model: string, @@ -245,13 +245,13 @@ const sqlGetUniqueTags = async () => sql` ORDER BY tag ASC `.then(({ rows }) => rows.map(row => row.tag as string)); -const sqlGetUniqueDevices = async () => sql` - SELECT DISTINCT make||' '||model as device, make, model FROM photos +const sqlGetUniqueCameras = async () => sql` + SELECT DISTINCT make||' '||model as camera, make, model FROM photos WHERE hidden IS NOT TRUE - ORDER BY device ASC + ORDER BY camera ASC `.then(({ rows }) => rows.map(({ make, model }) => ({ - deviceKey: createDeviceKey(make, model), - device: { make, model } as Device, + cameraKey: createCameraKey(make, model), + camera: { make, model } as Camera, }))); export type GetPhotosOptions = { @@ -259,7 +259,7 @@ export type GetPhotosOptions = { limit?: number offset?: number tag?: string - device?: Device + camera?: Camera takenBefore?: Date takenAfterInclusive?: Date includeHidden?: boolean @@ -299,7 +299,7 @@ export const getPhotos = async (options: GetPhotosOptions = {}) => { limit, offset, tag, - device, + camera, takenBefore, takenAfterInclusive, includeHidden, @@ -316,8 +316,8 @@ export const getPhotos = async (options: GetPhotosOptions = {}) => { getPhotosSql = () => sqlGetPhotosTakenAfterDateInclusive(takenAfterInclusive, limit); } else if (tag) { getPhotosSql = () => sqlGetPhotosByTag(limit, offset, tag); - } else if (device) { - getPhotosSql = () => sqlGetPhotosByDevice(limit, device.make, device.model); + } else if (camera) { + getPhotosSql = () => sqlGetPhotosByCamera(limit, camera.make, camera.model); } else if (sortBy === 'createdAt') { getPhotosSql = () => sqlGetPhotosSortedByCreatedAt(limit, offset); } else if (sortBy === 'priority') { @@ -344,4 +344,4 @@ export const getPhotosCountIncludingHidden = () => export const getUniqueTags = () => safelyQueryPhotos(sqlGetUniqueTags); -export const getUniqueDevices = () => safelyQueryPhotos(sqlGetUniqueDevices); +export const getUniqueCameras = () => safelyQueryPhotos(sqlGetUniqueCameras); diff --git a/src/site/paths.ts b/src/site/paths.ts index af8a8e3d..53cd18e9 100644 --- a/src/site/paths.ts +++ b/src/site/paths.ts @@ -1,15 +1,15 @@ import { Photo } from '@/photo'; import { BASE_URL } from './config'; import { - Device, - createDeviceKey, - getMakeModelFromDeviceString, -} from '@/device'; + Camera, + createCameraKey, + getMakeModelFromCameraString, +} from '@/camera'; // Prefixes const PREFIX_PHOTO = '/p'; const PREFIX_TAG = '/t'; -const PREFIX_DEVICE = '/shot-on'; +const PREFIX_CAMERA = '/shot-on'; // Modifiers const SHARE = 'share'; @@ -52,20 +52,20 @@ const getPhotoId = (photoOrPhotoId: PhotoOrPhotoId) => export const pathForPhoto = ( photo: PhotoOrPhotoId, tag?: string, - device?: Device, + camera?: Camera, ) => tag ? `${pathForTag(tag)}/${getPhotoId(photo)}` - : device - ? `${pathForDevice(device)}/${getPhotoId(photo)}` + : camera + ? `${pathForCamera(camera)}/${getPhotoId(photo)}` : `${PREFIX_PHOTO}/${getPhotoId(photo)}`; export const pathForPhotoShare = ( photo: PhotoOrPhotoId, tag?: string, - device?: Device, + camera?: Camera, ) => - `${pathForPhoto(photo, tag, device)}/${SHARE}`; + `${pathForPhoto(photo, tag, camera)}/${SHARE}`; export const pathForPhotoEdit = (photo: PhotoOrPhotoId) => `${PATH_ADMIN_PHOTOS}/${getPhotoId(photo)}/edit`; @@ -76,24 +76,24 @@ export const pathForTag = (tag: string) => export const pathForTagShare = (tag: string) => `${pathForTag(tag)}/${SHARE}`; -export const pathForDevice = ({ make, model }: Device) => - `${PREFIX_DEVICE}/${createDeviceKey(make, model)}`; +export const pathForCamera = ({ make, model }: Camera) => + `${PREFIX_CAMERA}/${createCameraKey(make, model)}`; -export const pathForDeviceShare = (device: Device) => - `${pathForDevice(device)}/${SHARE}`; +export const pathForCameraShare = (camera: Camera) => + `${pathForCamera(camera)}/${SHARE}`; export const absolutePathForPhoto = ( photo: PhotoOrPhotoId, tag?: string, - device?: Device, + camera?: Camera, ) => - `${BASE_URL}${pathForPhoto(photo, tag, device)}`; + `${BASE_URL}${pathForPhoto(photo, tag, camera)}`; export const absolutePathForTag = (tag: string) => `${BASE_URL}${pathForTag(tag)}`; -export const absolutePathForDevice= (device: Device) => - `${BASE_URL}${pathForDevice(device)}`; +export const absolutePathForCamera= (camera: Camera) => + `${BASE_URL}${pathForCamera(camera)}`; export const absolutePathForPhotoImage = (photo: PhotoOrPhotoId) => `${absolutePathForPhoto(photo)}/image`; @@ -101,8 +101,8 @@ export const absolutePathForPhotoImage = (photo: PhotoOrPhotoId) => export const absolutePathForTagImage = (tag: string) => `${absolutePathForTag(tag)}/image`; -export const absolutePathForDeviceImage= (device: Device) => - `${absolutePathForDevice(device)}/image`; +export const absolutePathForCameraImage= (camera: Camera) => + `${absolutePathForCamera(camera)}/image`; // p/[photoId] export const isPathPhoto = (pathname = '') => @@ -128,20 +128,20 @@ export const isPathTagPhoto = (pathname = '') => export const isPathTagPhotoShare = (pathname = '') => /^\/t\/[^/]+\/[^/]+\/share\/?$/.test(pathname); -// shot-on/[device] -export const isPathDevice = (pathname = '') => +// shot-on/[camera] +export const isPathCamera = (pathname = '') => /^\/shot-on\/[^/]+\/?$/.test(pathname); -// shot-on/[device]/share -export const isPathDeviceShare = (pathname = '') => +// shot-on/[camera]/share +export const isPathCameraShare = (pathname = '') => /^\/shot-on\/[^/]+\/share\/?$/.test(pathname); -// shot-on/[device]/[photoId] -export const isPathDevicePhoto = (pathname = '') => +// shot-on/[camera]/[photoId] +export const isPathCameraPhoto = (pathname = '') => /^\/shot-on\/[^/]+\/[^/]+\/?$/.test(pathname); -// shot-on/[device]/[photoId]/share -export const isPathDevicePhotoShare = (pathname = '') => +// shot-on/[camera]/[photoId]/share +export const isPathCameraPhotoShare = (pathname = '') => /^\/shot-on\/[^/]+\/[^/]+\/share\/?$/.test(pathname); export const isPathGrid = (pathname = '') => @@ -160,40 +160,40 @@ export const isPathProtected = (pathname = '') => export const getPathComponents = (pathname = ''): { photoId?: string tag?: string - device?: Device + camera?: Camera } => { const photoIdFromPhoto = pathname.match(/^\/p\/([^/]+)/)?.[1]; const photoIdFromTag = pathname.match(/^\/t\/[^/]+\/((?!share)[^/]+)/)?.[1]; // eslint-disable-next-line max-len - const photoIdFromDevice = pathname.match(/^\/shot-on\/[^/]+\/((?!share)[^/]+)/)?.[1]; + const photoIdFromCamera = pathname.match(/^\/shot-on\/[^/]+\/((?!share)[^/]+)/)?.[1]; const tag = pathname.match(/^\/t\/([^/]+)/)?.[1]; - const deviceString = pathname.match(/^\/shot-on\/([^/]+)/)?.[1]; - const device = deviceString - ? getMakeModelFromDeviceString(deviceString) + const cameraString = pathname.match(/^\/shot-on\/([^/]+)/)?.[1]; + const camera = cameraString + ? getMakeModelFromCameraString(cameraString) : undefined; return { photoId: ( photoIdFromPhoto || photoIdFromTag || - photoIdFromDevice + photoIdFromCamera ), tag, - device, + camera, }; }; export const getEscapePath = (pathname?: string) => { - const { photoId, tag, device } = getPathComponents(pathname); + const { photoId, tag, camera } = getPathComponents(pathname); if ( (photoId && isPathPhoto(pathname)) || (tag && isPathTag(pathname)) || - (device && isPathDevice(pathname)) + (camera && isPathCamera(pathname)) ) { return PATH_GRID; } else if (photoId && isPathTagPhotoShare(pathname)) { return pathForPhoto(photoId, tag); - } else if (photoId && isPathDevicePhotoShare(pathname)) { - return pathForPhoto(photoId, undefined, device); + } else if (photoId && isPathCameraPhotoShare(pathname)) { + return pathForPhoto(photoId, undefined, camera); } else if (photoId && isPathPhotoShare(pathname)) { return pathForPhoto(photoId); } else if (tag && ( @@ -201,10 +201,10 @@ export const getEscapePath = (pathname?: string) => { isPathTagShare(pathname) )) { return pathForTag(tag); - } else if (device && ( - isPathDevicePhoto(pathname) || - isPathDeviceShare(pathname) + } else if (camera && ( + isPathCameraPhoto(pathname) || + isPathCameraShare(pathname) )) { - return pathForDevice(device); + return pathForCamera(camera); } }; diff --git a/src/tag/index.ts b/src/tag/index.ts index f8de8a28..0d0d3df2 100644 --- a/src/tag/index.ts +++ b/src/tag/index.ts @@ -1,10 +1,10 @@ -import { Photo, descriptionForPhotoSet, labelForPhotos } from '@/photo'; +import { Photo, descriptionForPhotoSet, photoQuantityText } from '@/photo'; import { absolutePathForTag, absolutePathForTagImage } from '@/site/paths'; import { capitalizeWords } from '@/utility/string'; export const titleForTag = (tag: string, photos:Photo[]) => [ capitalizeWords(tag.replaceAll('-', ' ')), - `(${photos.length} ${labelForPhotos(photos)})`, + photoQuantityText(photos), ].join(' '); export const descriptionForTaggedPhotos = (