Refine camera/lens query handling

This commit is contained in:
Sam Becker 2025-03-16 16:48:48 -05:00
parent 0ca8823dae
commit 3966a6437a
8 changed files with 61 additions and 26 deletions

10
__tests__/lens.test.ts Normal file
View File

@ -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');
});
});

View File

@ -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 && <span className="whitespace-nowrap">
{' '}
<AiOutlineEyeInvisible
<IconHidden
className="inline translate-y-[-0.5px]"
size={16}
/>

View File

@ -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`;

View File

@ -28,7 +28,7 @@ export type Cameras = CameraWithCount[];
// Support keys for make-only and model-only camera queries
export const createCameraKey = ({ make, model }: Partial<Camera>) =>
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 = (

View File

@ -28,7 +28,7 @@ export type Lenses = LensWithCount[];
// Support keys for make-only and model-only lens queries
export const createLensKey = ({ make, model }: Partial<Lens>) =>
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':

View File

@ -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)`);

View File

@ -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
? <span className="inline-flex items-center gap-1">
{TAG_HIDDEN}
<AiOutlineEyeInvisible
<IconHidden
size={13}
className="translate-y-[-0.5px]"
/>
</span>
: TAG_HIDDEN}
href={pathForTag(TAG_HIDDEN)}
icon={!badged && <AiOutlineEyeInvisible size={16} />}
icon={!badged && <IconHidden size={16} />}
type={type}
className={className}
hoverEntity={countOnHover}

View File

@ -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