diff --git a/__tests__/lens.test.ts b/__tests__/lens.test.ts new file mode 100644 index 00000000..1a6533e9 --- /dev/null +++ b/__tests__/lens.test.ts @@ -0,0 +1,10 @@ +/* eslint-disable max-len */ +import { formatLensText, Lens } from '@/lens'; + +const IPHONE_15_PRO_FRONT: Lens = { make: 'Apple', model: 'iPhone 15 Pro front TrueDepth camera 2.69mm f/1.9' }; + +describe('Lens', () => { + it('correctly formats iPhone lenses', () => { + expect(formatLensText(IPHONE_15_PRO_FRONT)).toBe('Front Camera'); + }); +}); diff --git a/src/admin/AdminPhotosTable.tsx b/src/admin/AdminPhotosTable.tsx index 8aea502a..0c93e850 100644 --- a/src/admin/AdminPhotosTable.tsx +++ b/src/admin/AdminPhotosTable.tsx @@ -7,7 +7,6 @@ import PhotoSmall from '@/photo/PhotoSmall'; import { clsx } from 'clsx/lite'; import { pathForAdminPhotoEdit, pathForPhoto } from '@/app/paths'; import Link from 'next/link'; -import { AiOutlineEyeInvisible } from 'react-icons/ai'; import PhotoDate from '@/photo/PhotoDate'; import EditButton from './EditButton'; import { useAppState } from '@/state/AppState'; @@ -15,6 +14,7 @@ import { RevalidatePhoto } from '@/photo/InfinitePhotoScroll'; import PhotoSyncButton from './PhotoSyncButton'; import DeletePhotoButton from './DeletePhotoButton'; import { Timezone } from '@/utility/timezone'; +import IconHidden from '@/components/icons/IconHidden'; export default function AdminPhotosTable({ photos, @@ -71,7 +71,7 @@ export default function AdminPhotosTable({ {titleForPhoto(photo)} {photo.hidden && {' '} - diff --git a/src/app/paths.ts b/src/app/paths.ts index a62a4a37..d4f85a7c 100644 --- a/src/app/paths.ts +++ b/src/app/paths.ts @@ -140,13 +140,13 @@ export const pathForTag = (tag: string) => `${PREFIX_TAG}/${tag}`; export const pathForCamera = ({ make, model }: Camera) => - `${PREFIX_CAMERA}/${parameterize(make, true)}/${parameterize(model, true)}`; + `${PREFIX_CAMERA}/${parameterize(make)}/${parameterize(model)}`; export const pathForFilmSimulation = (simulation: FilmSimulation) => `${PREFIX_FILM_SIMULATION}/${simulation}`; export const pathForLens = ({ make, model }: Lens) => - `${PREFIX_LENS}/${parameterize(make, true)}/${parameterize(model, true)}`; + `${PREFIX_LENS}/${parameterize(make)}/${parameterize(model)}`; export const pathForFocalLength = (focal: number) => `${PREFIX_FOCAL_LENGTH}/${focal}mm`; diff --git a/src/camera/index.ts b/src/camera/index.ts index 288dae73..4377ae33 100644 --- a/src/camera/index.ts +++ b/src/camera/index.ts @@ -28,7 +28,7 @@ export type Cameras = CameraWithCount[]; // Support keys for make-only and model-only camera queries export const createCameraKey = ({ make, model }: Partial) => - parameterize(`${make ?? 'ANY'}-${model ?? 'ANY'}`, true); + parameterize(`${make ?? 'ANY'}-${model ?? 'ANY'}`); export const getCameraFromParams = ({ make, @@ -37,8 +37,8 @@ export const getCameraFromParams = ({ make: string, model: string, }): Camera => ({ - make: parameterize(make, true), - model: parameterize(model, true), + make: parameterize(make), + model: parameterize(model), }); export const sortCamerasWithCount = ( diff --git a/src/lens/index.ts b/src/lens/index.ts index 19c17dea..280793ae 100644 --- a/src/lens/index.ts +++ b/src/lens/index.ts @@ -28,7 +28,7 @@ export type Lenses = LensWithCount[]; // Support keys for make-only and model-only lens queries export const createLensKey = ({ make, model }: Partial) => - parameterize(`${make ?? 'ANY'}-${model ?? 'ANY'}`, true); + parameterize(`${make ?? 'ANY'}-${model ?? 'ANY'}`); export const getLensFromParams = ({ make, @@ -37,8 +37,8 @@ export const getLensFromParams = ({ make: string, model: string, }): Lens => ({ - make: parameterize(make, true), - model: parameterize(model, true), + make: parameterize(make), + model: parameterize(model), }); export const lensFromPhoto = ( @@ -55,13 +55,20 @@ const isLensMakeApple = (make?: string) => export const isLensApple = ({ make }: Lens) => isLensMakeApple(make); +const formatAppleLensText = (model: string) => { + if (model.includes('front TrueDepth camera')) { + return 'Front Camera'; + } + return model; +}; + export const formatLensText = ( { make, model: modelRaw }: Lens, length: 'long' | // Unmodified make and model 'medium' | // Make and model, with modifiers removed 'short' // Model only - = 'medium', + = 'short', ) => { // Capture simple make without modifiers like 'Corporation' or 'Company' const makeSimple = make.match(/^(\S+)/)?.[1]; @@ -69,7 +76,11 @@ export const formatLensText = ( makeSimple && modelRaw.toLocaleLowerCase().startsWith(makeSimple.toLocaleLowerCase()) ); - const model = modelRaw; + + const model = isLensMakeApple(make) + ? formatAppleLensText(modelRaw) + : modelRaw; + switch (length) { case 'long': case 'medium': diff --git a/src/photo/db/index.ts b/src/photo/db/index.ts index 1361fa96..37f20d33 100644 --- a/src/photo/db/index.ts +++ b/src/photo/db/index.ts @@ -9,10 +9,11 @@ export const PHOTO_DEFAULT_LIMIT = 100; // Trim whitespace // Make lowercase -// Replace spaces with dashes -// Remove periods and commas -const parameterizeForDb = (text: string) => - `REPLACE(REPLACE(REPLACE(LOWER(TRIM(${text})), '.', ''), ',', ''), ' ', '-')`; +// Remove commas +// Replace spaces, slashes with dashes +const parameterizeForDb = (field: string) => + // eslint-disable-next-line max-len + `REPLACE(REPLACE(REPLACE(LOWER(TRIM(${field})), ',', ''), '/', '-'), ' ', '-')`; export type GetPhotosOptions = { sortBy?: 'createdAt' | 'createdAtAsc' | 'takenAt' | 'priority' @@ -64,6 +65,19 @@ export const getWheresFromOptions = ( break; } + if (options.lens?.make) { + console.log('LENS MODEL QUERY',{ + db: parameterizeForDb('lens_make'), + args: parameterize(options.lens.make), + }); + } + if (options.lens?.model) { + console.log('LENS MODEL QUERY',{ + db: parameterizeForDb('lens_model'), + args: parameterize(options.lens.model), + }); + } + if (takenBefore) { wheres.push(`taken_at < $${valuesIndex++}`); wheresValues.push(takenBefore.toISOString()); @@ -87,19 +101,19 @@ export const getWheresFromOptions = ( } if (camera?.make) { wheres.push(`${parameterizeForDb('make')}=$${valuesIndex++}`); - wheresValues.push(parameterize(camera.make, true)); + wheresValues.push(parameterize(camera.make)); } if (camera?.model) { wheres.push(`${parameterizeForDb('model')}=$${valuesIndex++}`); - wheresValues.push(parameterize(camera.model, true)); + wheresValues.push(parameterize(camera.model)); } if (lens?.make) { wheres.push(`${parameterizeForDb('lens_make')}=$${valuesIndex++}`); - wheresValues.push(parameterize(lens.make, true)); + wheresValues.push(parameterize(lens.make)); } if (lens?.model) { wheres.push(`${parameterizeForDb('lens_model')}=$${valuesIndex++}`); - wheresValues.push(parameterize(lens.model, true)); + wheresValues.push(parameterize(lens.model)); } if (tag) { wheres.push(`$${valuesIndex++}=ANY(tags)`); diff --git a/src/tag/HiddenTag.tsx b/src/tag/HiddenTag.tsx index f82619fb..6998faa3 100644 --- a/src/tag/HiddenTag.tsx +++ b/src/tag/HiddenTag.tsx @@ -1,9 +1,9 @@ import { TAG_HIDDEN } from '.'; import { pathForTag } from '@/app/paths'; +import IconHidden from '@/components/icons/IconHidden'; import EntityLink, { EntityLinkExternalProps, } from '@/components/primitives/EntityLink'; -import { AiOutlineEyeInvisible } from 'react-icons/ai'; export default function HiddenTag({ type, @@ -20,14 +20,14 @@ export default function HiddenTag({ label={badged ? {TAG_HIDDEN} - : TAG_HIDDEN} href={pathForTag(TAG_HIDDEN)} - icon={!badged && } + icon={!badged && } type={type} className={className} hoverEntity={countOnHover} diff --git a/src/utility/string.ts b/src/utility/string.ts index f99f459b..80014a96 100644 --- a/src/utility/string.ts +++ b/src/utility/string.ts @@ -22,10 +22,10 @@ export const parameterize = ( ) => string .trim() - // Replaces spaces, underscores, and dashes with dashes - .replaceAll(/[\s_–—]/gi, '-') + // Replaces spaces, underscores, slashes,and dashes with dashes + .replaceAll(/[\s_–—/]/gi, '-') // Removes punctuation - .replaceAll(/['"!@#$%^&*()_+=[\]{};:/?,.<>\\|`~]/gi, '') + .replaceAll(/['"!@#$%^&*()_+=[\]{};:/?,<>\\|`~]/gi, '') // Removes all non-alphanumeric characters .replaceAll( shouldRemoveNonAlphanumeric