diff --git a/src/focal/index.ts b/src/focal/index.ts index 150e3317..6486f2c6 100644 --- a/src/focal/index.ts +++ b/src/focal/index.ts @@ -9,6 +9,11 @@ import { absolutePathForFocalLengthImage, } from '@/site/paths'; +export type FocalLengths = { + focal: number + count: number +}[] + export const getFocalLengthFromString = (focalString?: string) => { const focal = focalString?.match(/^([0-9]+)mm/)?.[1]; return focal ? parseInt(focal, 10) : 0; diff --git a/src/photo/cache.ts b/src/photo/cache.ts index d4b88072..51792c04 100644 --- a/src/photo/cache.ts +++ b/src/photo/cache.ts @@ -14,6 +14,7 @@ import { getPhotosNearId, getPhotosMostRecentUpdate, getPhotosMeta, + getUniqueFocalLengths, } from '@/photo/db/query'; import { GetPhotosOptions } from './db'; import { parseCachedPhotoDates, parseCachedPhotosDates } from '@/photo'; @@ -37,6 +38,7 @@ const KEY_PHOTO = 'photo'; const KEY_TAGS = 'tags'; const KEY_CAMERAS = 'cameras'; const KEY_FILM_SIMULATIONS = 'film-simulations'; +const KEY_FOCAL_LENGTHS = 'focal-lengths'; // Type keys const KEY_COUNT = 'count'; const KEY_HIDDEN = 'hidden'; @@ -196,6 +198,12 @@ export const getUniqueFilmSimulationsCached = [KEY_PHOTOS, KEY_FILM_SIMULATIONS], ); +export const getUniqueFocalLengthsCached = + unstable_cache( + getUniqueFocalLengths, + [KEY_PHOTOS, KEY_FOCAL_LENGTHS], + ); + // No store export const getPhotosNoStore = (...args: Parameters) => { diff --git a/src/photo/db/query.ts b/src/photo/db/query.ts index cda55524..c6c96d5c 100644 --- a/src/photo/db/query.ts +++ b/src/photo/db/query.ts @@ -21,6 +21,7 @@ import { getOrderByFromOptions, } from '.'; import { getWheresFromOptions } from '.'; +import { FocalLengths } from '@/focal'; const createPhotosTable = () => sql` @@ -283,6 +284,20 @@ export const getUniqueFilmSimulations = async () => }))) , 'getUniqueFilmSimulations'); +export const getUniqueFocalLengths = async () => + safelyQueryPhotos(() => sql` + SELECT DISTINCT focal_length, COUNT(*) + FROM photos + WHERE hidden IS NOT TRUE AND focal_length IS NOT NULL + GROUP BY focal_length + ORDER BY focal_length ASC + `.then(({ rows }): FocalLengths => rows + .map(({ focal_length, count }) => ({ + focal: parseInt(focal_length, 10), + count: parseInt(count, 10), + }))) + , 'getUniqueFocalLengths'); + export const getPhotos = async (options: GetPhotosOptions = {}) => safelyQueryPhotos(async () => { const sql = ['SELECT * FROM photos']; diff --git a/src/site/CommandK.tsx b/src/site/CommandK.tsx index f7a7c356..b47c2903 100644 --- a/src/site/CommandK.tsx +++ b/src/site/CommandK.tsx @@ -10,15 +10,18 @@ import { import { pathForCamera, pathForFilmSimulation, + pathForFocalLength, } from './paths'; import { formatCameraText } from '@/camera'; import { photoQuantityText } from '@/photo'; import { formatCount, formatCountDescriptive } from '@/utility/string'; -import { TagsWithMeta } from '@/tag'; import PhotoFilmSimulationIcon from '@/simulation/PhotoFilmSimulationIcon'; import { IoMdCamera } from 'react-icons/io'; import { ADMIN_DEBUG_TOOLS_ENABLED, SHOW_FILM_SIMULATIONS } from './config'; import { labelForFilmSimulation } from '@/vendors/fujifilm'; +import { getUniqueFocalLengths } from '@/photo/db/query'; +import { formatFocalLength } from '@/focal'; +import { TbCone } from 'react-icons/tb'; export default async function CommandK() { const [ @@ -26,15 +29,17 @@ export default async function CommandK() { tags, cameras, filmSimulations, + focalLengths, ] = await Promise.all([ getPhotosMetaCached() .then(({ count }) => count) .catch(() => 0), - getUniqueTagsCached().catch(() => [] as TagsWithMeta), + getUniqueTagsCached().catch(() => []), getUniqueCamerasCached().catch(() => []), SHOW_FILM_SIMULATIONS ? getUniqueFilmSimulationsCached().catch(() => []) : [], + getUniqueFocalLengths().catch(() => []), ]); const SECTION_CAMERAS: CommandKSection = { @@ -61,11 +66,25 @@ export default async function CommandK() { })), }; + const SECTION_FOCAL: CommandKSection = { + heading: 'Focal Lengths', + accessory: , + items: focalLengths.map(({ focal, count }) => ({ + label: formatFocalLength(focal)!, + annotation: formatCount(count), + annotationAria: formatCountDescriptive(count), + path: pathForFocalLength(focal), + })), + }; + return