diff --git a/__tests__/camera.test.ts b/__tests__/camera.test.ts index 41f11eb3..e4950b68 100644 --- a/__tests__/camera.test.ts +++ b/__tests__/camera.test.ts @@ -1,14 +1,30 @@ import { Camera, formatCameraText } from '@/camera'; +const APPLE : Camera = { make: 'Apple', model: 'iPhone 11 Pro' }; +const APPLE_01 : Camera = { make: 'Apple', model: 'iPhone 11' }; +const APPLE_02 : Camera = { make: 'Apple', model: 'iPhone 15 Pro Max' }; +const FUJIFILM : Camera = { make: 'Fujifilm', model: 'X-T5' }; +const CANON : Camera = { make: 'Canon', model: 'Canon EOS 800D' }; +const NIKON : Camera = { + make: 'Nikon Corporation', + model: 'Nikon D7000', +}; + describe('Camera', () => { - it('labels correctly', () => { - const apple: Camera = { make: 'Apple', model: 'iPhone 11 Pro' }; - expect(formatCameraText(apple, true)).toBe('Apple iPhone 11 Pro'); - expect(formatCameraText(apple, false)).toBe('iPhone 11 Pro'); - const fujifilm: Camera = { make: 'Fujifilm', model: 'X-T5' }; - expect(formatCameraText(fujifilm)).toBe('Fujifilm X-T5'); - const canon: Camera = { make: 'Canon', model: 'Canon EOS 800D' }; - expect(formatCameraText(canon)).toBe('Canon EOS 800D'); + it('labels full text correctly', () => { + expect(formatCameraText(APPLE)).toBe('iPhone 11 Pro'); + expect(formatCameraText(APPLE, 'always')).toBe('Apple iPhone 11 Pro'); + expect(formatCameraText(APPLE, 'if-not-apple')).toBe('iPhone 11 Pro'); + expect(formatCameraText(APPLE, 'never')).toBe('iPhone 11 Pro'); + expect(formatCameraText(FUJIFILM)).toBe('Fujifilm X-T5'); + expect(formatCameraText(CANON)).toBe('Canon EOS 800D'); + expect(formatCameraText(NIKON)).toBe('Nikon D7000'); + }); + it('labels models correctly', () => { + expect(formatCameraText(APPLE, 'never')).toBe('iPhone 11 Pro'); + expect(formatCameraText(APPLE, 'never', true)).toBe('11 Pro'); + expect(formatCameraText(APPLE_01, 'never', true)).toBe('iPhone 11'); + expect(formatCameraText(APPLE_02, 'never', true)).toBe('15 Pro Max'); }); }); diff --git a/__tests__/path.test.ts b/__tests__/path.test.ts index 4263daef..858dfede 100644 --- a/__tests__/path.test.ts +++ b/__tests__/path.test.ts @@ -18,12 +18,12 @@ import { isPathTagPhotoShare, isPathTagShare, } from '@/site/paths'; -import { getCameraFromKey } from '@/camera'; const PHOTO_ID = 'UsKSGcbt'; const TAG = 'tag-name'; -const CAMERA = 'fujifilm-x-t1'; -const CAMERA_OBJECT = getCameraFromKey(CAMERA); +const CAMERA_MAKE = 'fujifilm'; +const CAMERA_MODEL = 'x-t1'; +const CAMERA_OBJECT = { make: CAMERA_MAKE, model: CAMERA_MODEL }; const FILM_SIMULATION = 'acros'; const SHARE = 'share'; @@ -39,7 +39,7 @@ 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_CAMERA = `/shot-on/${CAMERA}`; +const PATH_CAMERA = `/shot-on/${CAMERA_MAKE}/${CAMERA_MODEL}`; const PATH_CAMERA_SHARE = `${PATH_CAMERA}/${SHARE}`; const PATH_CAMERA_PHOTO = `${PATH_CAMERA}/${PHOTO_ID}`; const PATH_CAMERA_PHOTO_SHARE = `${PATH_CAMERA_PHOTO}/${SHARE}`; diff --git a/package.json b/package.json index c524d398..a6fa2f8d 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "nanoid": "^5.0.6", - "next": "14.2.0-canary.39", + "next": "14.2.0-canary.49", "next-auth": "5.0.0-beta.15", "next-themes": "^0.3.0", "openai": "^4.29.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index efa50d43..8d43426c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -52,7 +52,7 @@ dependencies: version: 1.0.1 '@vercel/analytics': specifier: ^1.2.2 - version: 1.2.2(next@14.2.0-canary.39)(react@18.2.0) + version: 1.2.2(next@14.2.0-canary.49)(react@18.2.0) '@vercel/blob': specifier: ^0.22.1 version: 0.22.1 @@ -64,7 +64,7 @@ dependencies: version: 0.7.2 '@vercel/speed-insights': specifier: ^1.0.10 - version: 1.0.10(next@14.2.0-canary.39)(react@18.2.0)(svelte@4.2.12)(vue@3.4.21) + version: 1.0.10(next@14.2.0-canary.49)(react@18.2.0)(svelte@4.2.12)(vue@3.4.21) ai: specifier: ^3.0.13 version: 3.0.13(react@18.2.0)(solid-js@1.8.15)(svelte@4.2.12)(vue@3.4.21)(zod@3.22.4) @@ -105,11 +105,11 @@ dependencies: specifier: ^5.0.6 version: 5.0.6 next: - specifier: 14.2.0-canary.39 - version: 14.2.0-canary.39(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0) + specifier: 14.2.0-canary.49 + version: 14.2.0-canary.49(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0) next-auth: specifier: 5.0.0-beta.15 - version: 5.0.0-beta.15(next@14.2.0-canary.39)(react@18.2.0) + version: 5.0.0-beta.15(next@14.2.0-canary.49)(react@18.2.0) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.2.0)(react@18.2.0) @@ -1563,8 +1563,8 @@ packages: - utf-8-validate dev: false - /@next/env@14.2.0-canary.39: - resolution: {integrity: sha512-ROeqwq9mybhzfdzNDbz9/0e3fFB6gtC25NZNC/rhZzvgkTvUuYXUbJOJSvvtsoUjQolTCFOhZqKmopX+QgwYwQ==} + /@next/env@14.2.0-canary.49: + resolution: {integrity: sha512-rQaBRv0PRO3+4lx90zB9eBL0xk230G+6avgCyBL272hckH4XsGgXY6adtBBmZJF1QuDI+pS+DqppXSJvfexsdw==} dev: false /@next/eslint-plugin-next@14.1.4: @@ -1573,8 +1573,8 @@ packages: glob: 10.3.10 dev: false - /@next/swc-darwin-arm64@14.2.0-canary.39: - resolution: {integrity: sha512-ImAEFQBac/jYFCQYAEOxLZlzZfoa0GnbmXlGruzyNXl7RG3gJ3OBXx6G/puySAdytp54tArmr+0h+xoEXbop2Q==} + /@next/swc-darwin-arm64@14.2.0-canary.49: + resolution: {integrity: sha512-tFFCgRJOk28rIiEGjz2bafqp3G5lV7hXyYjZ7d+gt/MjpLRrtTwu+lRBv/W1VFdTkPv8+k2hvXZNNTHO1n57Ow==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -1582,8 +1582,8 @@ packages: dev: false optional: true - /@next/swc-darwin-x64@14.2.0-canary.39: - resolution: {integrity: sha512-2q0F3L261vYPOrn7KXLX5SzfMe8yPRs0plnExpV2MwQjikt5OhlUdGwRRyEFT0DS0S0cyaKw00nENxBuDi7VyA==} + /@next/swc-darwin-x64@14.2.0-canary.49: + resolution: {integrity: sha512-NR4Meb67q8M2pNP5a8Tp3Zfar2Ao8ChHWcD3wEBgICcgJ4ZyCQCWXdM+VBsf8a3yuAoXmu1/cwOwWu1KXVC96A==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -1591,8 +1591,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-gnu@14.2.0-canary.39: - resolution: {integrity: sha512-efraDAfAjQosfUdW8ZMjnrH3/mveQQxs055BdGfh+L0+hlTf05ECUH07tg3AKqihhnk+sgJUqigR5ZSsUYrqsw==} + /@next/swc-linux-arm64-gnu@14.2.0-canary.49: + resolution: {integrity: sha512-2bFQUNYnz6L7xOAzvejMj09iqmWwkjFyguGEfmNiFN0kPgJ4viSCKZvoiuG/MPh3VoDSz5N2qx1tehSCy7KbFA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1600,8 +1600,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-musl@14.2.0-canary.39: - resolution: {integrity: sha512-Eb6+d3XkhwaEd69OoTOa4/scqQJtCUiZrmWjR0sVbW3QJ0wWu2o5gz8mInYsLeLwxN+HDy1aDuQSl3hp2PwBbQ==} + /@next/swc-linux-arm64-musl@14.2.0-canary.49: + resolution: {integrity: sha512-68PjCGC1JghA2tuznu+ExeSP+L6qpf6afblB4wFhDRniP+0hRrZB+1E3jJ3PmBgHtitJJMaplTFeKYQ8xbF8xw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1609,8 +1609,8 @@ packages: dev: false optional: true - /@next/swc-linux-x64-gnu@14.2.0-canary.39: - resolution: {integrity: sha512-HKkx1WCMsycDFOp76avVMCIGm/E0jw3yugfyIc/g1vRIh6fTOZ9iyLd1Uannu4MorTxGWS4g1ZRr1C5/9Ve8kg==} + /@next/swc-linux-x64-gnu@14.2.0-canary.49: + resolution: {integrity: sha512-eiDvo0bnYCI59UhaZrNV1k7wZPFHyQ2uJ7/MUH9yvZZcSKBxRDtNc3FmCAZjKiNx/SclMFRAtENLOlDzceRp5g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1618,8 +1618,8 @@ packages: dev: false optional: true - /@next/swc-linux-x64-musl@14.2.0-canary.39: - resolution: {integrity: sha512-9W/UTFugvG0fYhNK5IqahiwldH3JSXmF2iCzQMbGMpyhjvOn1UirEZPwkMXz6tdSGXVHwxvvsuhZhZgBIt8csw==} + /@next/swc-linux-x64-musl@14.2.0-canary.49: + resolution: {integrity: sha512-XgwiLB/WkRjuhWoKZmlRsZl1b8C7dsYlRD3zqHPkrgWhERyyn3AoeRjIa/eHR6nxj7oTu2KHET1oSJoYobH70g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1627,8 +1627,8 @@ packages: dev: false optional: true - /@next/swc-win32-arm64-msvc@14.2.0-canary.39: - resolution: {integrity: sha512-rtG2wYP3Sa67F2AqaX2qISefZbc/KN0fj5gPx3ReFIuK8/p6tR/L063xvyNmBZs22DZuc07EaFCQ9Px7EB0C2Q==} + /@next/swc-win32-arm64-msvc@14.2.0-canary.49: + resolution: {integrity: sha512-jqC5vhFOAewsGdWriuQqR2aalQ8dHJ1WkSl1psluTxpo5UgICBk+H0wQ93a0CEfD0Rj+8QjUFh+U1oYTqE4YIg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -1636,8 +1636,8 @@ packages: dev: false optional: true - /@next/swc-win32-ia32-msvc@14.2.0-canary.39: - resolution: {integrity: sha512-Qh3vNCQQqghFuX4XhKuBhlleaRNIVFTspFMMKdQKFoATVVZh5n/PEeGEIgwjZjsjwfLPI82fkIvxhZkPujcAgg==} + /@next/swc-win32-ia32-msvc@14.2.0-canary.49: + resolution: {integrity: sha512-Zcfe1+FuFtMCtG0L7F9yh0yRhmLM2gGAUHW41FYN+Rtbi/JFS8qhs/M7pOPkqhEWWKqo3at64q7z8KQh+21VsQ==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] @@ -1645,8 +1645,8 @@ packages: dev: false optional: true - /@next/swc-win32-x64-msvc@14.2.0-canary.39: - resolution: {integrity: sha512-CPFzgcPYamtJpHtrHr55LsZ9g95l9vnm85OckaDQCK+359z4sgWk5Jp2ortPN/ZorDk+KjiixrE8x1Ix07Mk9g==} + /@next/swc-win32-x64-msvc@14.2.0-canary.49: + resolution: {integrity: sha512-yeCjnmqMmI9aNbRk3DTrKvCuImUWXU+Kl0XC9KFo8iLpOztpCQrMA+pf5s3GRqv1HRzbRoHsj+1VCPXzTmZrLA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -3147,7 +3147,7 @@ packages: crypto-js: 4.2.0 dev: false - /@vercel/analytics@1.2.2(next@14.2.0-canary.39)(react@18.2.0): + /@vercel/analytics@1.2.2(next@14.2.0-canary.49)(react@18.2.0): resolution: {integrity: sha512-X0rctVWkQV1e5Y300ehVNqpOfSOufo7ieA5PIdna8yX/U7Vjz0GFsGf4qvAhxV02uQ2CVt7GYcrFfddXXK2Y4A==} peerDependencies: next: '>= 13' @@ -3158,7 +3158,7 @@ packages: react: optional: true dependencies: - next: 14.2.0-canary.39(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0) + next: 14.2.0-canary.49(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 server-only: 0.0.1 dev: false @@ -3190,7 +3190,7 @@ packages: ws: 8.14.2(bufferutil@4.0.8)(utf-8-validate@6.0.3) dev: false - /@vercel/speed-insights@1.0.10(next@14.2.0-canary.39)(react@18.2.0)(svelte@4.2.12)(vue@3.4.21): + /@vercel/speed-insights@1.0.10(next@14.2.0-canary.49)(react@18.2.0)(svelte@4.2.12)(vue@3.4.21): resolution: {integrity: sha512-4uzdKB0RW6Ff2FkzshzjZ+RlJfLPxgm/00i0XXgxfMPhwnnsk92YgtqsxT9OcPLdJUyVU1DqFlSWWjIQMPkh0g==} requiresBuild: true peerDependencies: @@ -3214,7 +3214,7 @@ packages: vue-router: optional: true dependencies: - next: 14.2.0-canary.39(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0) + next: 14.2.0-canary.49(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 svelte: 4.2.12 vue: 3.4.21(typescript@5.4.3) @@ -3227,7 +3227,7 @@ packages: '@vue/shared': 3.4.21 entities: 4.5.0 estree-walker: 2.0.2 - source-map-js: 1.0.2 + source-map-js: 1.2.0 dev: false /@vue/compiler-dom@3.4.21: @@ -3248,7 +3248,7 @@ packages: estree-walker: 2.0.2 magic-string: 0.30.8 postcss: 8.4.38 - source-map-js: 1.0.2 + source-map-js: 1.2.0 dev: false /@vue/compiler-ssr@3.4.21: @@ -3822,10 +3822,6 @@ packages: engines: {node: '>=16'} dev: false - /caniuse-lite@1.0.30001591: - resolution: {integrity: sha512-PCzRMei/vXjJyL5mJtzNiUCKP59dm8Apqc3PH8gJkMnMXZGox93RbE76jHsmLwmIo6/3nsYIpJtx0O7u5PqFuQ==} - dev: false - /caniuse-lite@1.0.30001600: resolution: {integrity: sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==} dev: false @@ -4037,7 +4033,7 @@ packages: engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} dependencies: mdn-data: 2.0.30 - source-map-js: 1.0.2 + source-map-js: 1.2.0 dev: false /css.escape@1.5.1: @@ -6381,7 +6377,7 @@ packages: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: false - /next-auth@5.0.0-beta.15(next@14.2.0-canary.39)(react@18.2.0): + /next-auth@5.0.0-beta.15(next@14.2.0-canary.49)(react@18.2.0): resolution: {integrity: sha512-UQggNq8CDu3/w8CYkihKLLnRPNXel98K0j7mtjj9a6XTNYo4Hni8xg/2h1YhElW6vXE8mgtvmH11rU8NKw86jQ==} peerDependencies: '@simplewebauthn/browser': ^9.0.1 @@ -6398,7 +6394,7 @@ packages: optional: true dependencies: '@auth/core': 0.28.0 - next: 14.2.0-canary.39(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0) + next: 14.2.0-canary.49(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 dev: false @@ -6412,40 +6408,43 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /next@14.2.0-canary.39(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-sTAsUnf7ihBdvN0XwiPKe6kfqxUeEZJaHVOR5RIt2LJ2OnI1mVAp875hjKNxDeOxg2TjpxQCWiEEeKE8IV/tvw==} + /next@14.2.0-canary.49(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-sfryWP84xmqUOYAilbiojczTpTGCRTMch3w+EVppzPj0DS6gOWv9vPUHp/6uMWWZ+YX+n3GkYhwRK80Q+FG+kg==} engines: {node: '>=18.17.0'} hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 react: ^18.2.0 react-dom: ^18.2.0 sass: ^1.3.0 peerDependenciesMeta: '@opentelemetry/api': optional: true + '@playwright/test': + optional: true sass: optional: true dependencies: - '@next/env': 14.2.0-canary.39 + '@next/env': 14.2.0-canary.49 '@swc/helpers': 0.5.5 busboy: 1.6.0 - caniuse-lite: 1.0.30001591 + caniuse-lite: 1.0.30001600 graceful-fs: 4.2.11 postcss: 8.4.31 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) styled-jsx: 5.1.1(@babel/core@7.23.9)(react@18.2.0) optionalDependencies: - '@next/swc-darwin-arm64': 14.2.0-canary.39 - '@next/swc-darwin-x64': 14.2.0-canary.39 - '@next/swc-linux-arm64-gnu': 14.2.0-canary.39 - '@next/swc-linux-arm64-musl': 14.2.0-canary.39 - '@next/swc-linux-x64-gnu': 14.2.0-canary.39 - '@next/swc-linux-x64-musl': 14.2.0-canary.39 - '@next/swc-win32-arm64-msvc': 14.2.0-canary.39 - '@next/swc-win32-ia32-msvc': 14.2.0-canary.39 - '@next/swc-win32-x64-msvc': 14.2.0-canary.39 + '@next/swc-darwin-arm64': 14.2.0-canary.49 + '@next/swc-darwin-x64': 14.2.0-canary.49 + '@next/swc-linux-arm64-gnu': 14.2.0-canary.49 + '@next/swc-linux-arm64-musl': 14.2.0-canary.49 + '@next/swc-linux-x64-gnu': 14.2.0-canary.49 + '@next/swc-linux-x64-musl': 14.2.0-canary.49 + '@next/swc-win32-arm64-msvc': 14.2.0-canary.49 + '@next/swc-win32-ia32-msvc': 14.2.0-canary.49 + '@next/swc-win32-x64-msvc': 14.2.0-canary.49 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -6843,7 +6842,7 @@ packages: dependencies: nanoid: 3.3.7 picocolors: 1.0.0 - source-map-js: 1.0.2 + source-map-js: 1.2.0 dev: false /postcss@8.4.38: @@ -7341,11 +7340,6 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /source-map-js@1.0.2: - resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} - engines: {node: '>=0.10.0'} - dev: false - /source-map-js@1.2.0: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} diff --git a/src/app/admin/baseline/page.tsx b/src/app/admin/baseline/page.tsx new file mode 100644 index 00000000..181682d9 --- /dev/null +++ b/src/app/admin/baseline/page.tsx @@ -0,0 +1,194 @@ +'use client'; + +import DivDebugBaselineGrid from '@/components/DivDebugBaselineGrid'; +import FieldSetWithStatus from '@/components/FieldSetWithStatus'; +import SiteGrid from '@/components/SiteGrid'; +import EntityLink from '@/components/primitives/EntityLink'; +import LabeledIcon from '@/components/primitives/LabeledIcon'; +import PhotoFilmSimulationIcon from '@/simulation/PhotoFilmSimulationIcon'; +import { useAppState } from '@/state'; +import { clsx } from 'clsx/lite'; +import { useState } from 'react'; +import { FaCamera, FaHandSparkles, FaUserAltSlash } from 'react-icons/fa'; +import { IoMdCamera } from 'react-icons/io'; +import { IoImageSharp } from 'react-icons/io5'; + +const DEBUG_LINES = new Array(22).fill(null); + +export default function ComponentsPage() { + const { + shouldShowBaselineGrid, + setShouldShowBaselineGrid, + } = useAppState(); + + const [debugComponents, setDebugComponents] = useState(false); + + return ( + +

+
+ Baseline Grid: + + 13.5px / 19px + 14px / 20px + +
+
*]:inline-flex [&>*]:gap-1 [&_input]:-translate-y-0.5', + )}> + setShouldShowBaselineGrid?.(e === 'true')} + /> + setDebugComponents(e === 'true')} + /> +
+

+ +
+
+ } + debug={debugComponents} + > + Camera
Line two +
+
+
+ } debug={debugComponents}> + Image + +
+
+ } debug={debugComponents}> + Image + +
+
+ } debug={debugComponents}> + Image + +
+
+ } debug={debugComponents}> + Image + +
+
+ } debug={debugComponents}> + Image + +
+
+ } debug={debugComponents}> + Image + +
+
+ } debug={debugComponents}> + Image + +
+
+ } debug={debugComponents}> + Image + +
+
+ } debug={debugComponents}> + Image + +
+
+ } debug={debugComponents}> + Image + +
+
+ } + label="Image" + debug={debugComponents} + /> +
+
+ } + label="Image" + badged + debug={debugComponents} + /> +
+
+ } + debug={debugComponents} + > + Canon Mark III + +
+
+ } + label="Astia/Soft" + type="icon-last" + iconWide + badged + debug={debugComponents} + /> +
+
+ } debug={debugComponents}> + Image + +
+
+ } debug={debugComponents}> + Image + +
+
+ } + label="Astia/Soft" + type="icon-last" + badged + debug={debugComponents} + /> +
+
+ } debug={debugComponents}> + Image + +
+
+ } debug={debugComponents}> + Image + +
+
+
*]:bg-gray-800', + '[&>*]:flex', + )}> + {DEBUG_LINES.map((_, i) => +
+ Line {(i + 1).toString().padStart(2, '0')} +
+ )} +
+
+ } + /> + ); +} diff --git a/src/app/admin/photos/page.tsx b/src/app/admin/photos/page.tsx index bfec9b18..abbade12 100644 --- a/src/app/admin/photos/page.tsx +++ b/src/app/admin/photos/page.tsx @@ -32,6 +32,7 @@ import { getStoragePhotoUrlsNoStore } from '@/services/storage/cache'; import MoreComponentsFromSearchParams from '@/components/MoreComponentsFromSearchParams'; import { getPhotos } from '@/services/vercel-postgres'; +import PhotoDate from '@/photo/PhotoDate'; const DEBUG_PHOTO_BLOBS = false; @@ -103,7 +104,7 @@ export default async function AdminPhotosPage({ 'lg:w-[50%] uppercase', 'text-dim', )}> - {photo.takenAtNaive} +
getPhotoCached(photoId)); -interface PhotoCameraProps { - params: { photoId: string, camera: string } -} - export async function generateMetadata({ - params: { photoId, camera }, + params: { photoId, make, model }, }: PhotoCameraProps): Promise { const photo = await getPhotoCachedCached(photoId); @@ -35,7 +31,7 @@ export async function generateMetadata({ const url = absolutePathForPhoto( photo, undefined, - cameraFromPhoto(photo, camera), + cameraFromPhoto(photo, { make, model }), ); return { @@ -57,14 +53,14 @@ export async function generateMetadata({ } export default async function PhotoCameraPage({ - params: { photoId, camera: cameraProp }, + params: { photoId, make, model }, children, }: PhotoCameraProps & { children: ReactNode }) { const photo = await getPhotoCachedCached(photoId); if (!photo) { redirect(PATH_ROOT); } - const camera = cameraFromPhoto(photo, cameraProp); + const camera = cameraFromPhoto(photo, { make, model }); const [ photos, diff --git a/src/app/shot-on/[camera]/[photoId]/page.tsx b/src/app/shot-on/[make]/[model]/[photoId]/page.tsx similarity index 100% rename from src/app/shot-on/[camera]/[photoId]/page.tsx rename to src/app/shot-on/[make]/[model]/[photoId]/page.tsx diff --git a/src/app/shot-on/[camera]/[photoId]/share/page.tsx b/src/app/shot-on/[make]/[model]/[photoId]/share/page.tsx similarity index 65% rename from src/app/shot-on/[camera]/[photoId]/share/page.tsx rename to src/app/shot-on/[make]/[model]/[photoId]/share/page.tsx index 15e02113..2c932248 100644 --- a/src/app/shot-on/[camera]/[photoId]/share/page.tsx +++ b/src/app/shot-on/[make]/[model]/[photoId]/share/page.tsx @@ -1,19 +1,17 @@ import { getPhotoCached } from '@/photo/cache'; -import { cameraFromPhoto } from '@/camera'; +import { PhotoCameraProps, cameraFromPhoto } from '@/camera'; import PhotoShareModal from '@/photo/PhotoShareModal'; import { PATH_ROOT } from '@/site/paths'; import { redirect } from 'next/navigation'; export default async function Share({ - params: { photoId, camera: cameraProp }, -}: { - params: { photoId: string, camera: string } -}) { + params: { photoId, make, model }, +}: PhotoCameraProps) { const photo = await getPhotoCached(photoId); if (!photo) { return redirect(PATH_ROOT); } - const camera = cameraFromPhoto(photo, cameraProp); + const camera = cameraFromPhoto(photo, { make, model }); return ; } diff --git a/src/app/shot-on/[camera]/image/route.tsx b/src/app/shot-on/[make]/[model]/image/route.tsx similarity index 86% rename from src/app/shot-on/[camera]/image/route.tsx rename to src/app/shot-on/[make]/[model]/image/route.tsx index 209454e0..cebecea5 100644 --- a/src/app/shot-on/[camera]/image/route.tsx +++ b/src/app/shot-on/[make]/[model]/image/route.tsx @@ -1,5 +1,5 @@ import { getPhotosCached } from '@/photo/cache'; -import { getCameraFromKey } from '@/camera'; +import { CameraProps, getCameraFromParams } from '@/camera'; import { IMAGE_OG_DIMENSION_SMALL, MAX_PHOTOS_TO_SHOW_PER_TAG, @@ -13,9 +13,9 @@ export const runtime = 'edge'; export async function GET( _: Request, - context: { params: { camera: string } }, + context: CameraProps, ) { - const camera = getCameraFromKey(context.params.camera); + const camera = getCameraFromParams(context.params); const [ photos, diff --git a/src/app/shot-on/[camera]/page.tsx b/src/app/shot-on/[make]/[model]/page.tsx similarity index 86% rename from src/app/shot-on/[camera]/page.tsx rename to src/app/shot-on/[make]/[model]/page.tsx index e528a6ce..4dfc64bc 100644 --- a/src/app/shot-on/[camera]/page.tsx +++ b/src/app/shot-on/[make]/[model]/page.tsx @@ -1,5 +1,5 @@ -import { getCameraFromKey } from '@/camera'; import { Metadata } from 'next/types'; +import { CameraProps, getCameraFromParams } from '@/camera'; import { generateMetaForCamera } from '@/camera/meta'; import { GRID_THUMBNAILS_TO_SHOW_MAX } from '@/photo'; import { PaginationParams } from '@/site/pagination'; @@ -9,14 +9,10 @@ import { } from '@/camera/data'; import CameraOverview from '@/camera/CameraOverview'; -interface CameraProps { - params: { camera: string }, -} - export async function generateMetadata({ params, }: CameraProps): Promise { - const camera = getCameraFromKey(params.camera); + const camera = getCameraFromParams(params); const [ photos, @@ -54,7 +50,7 @@ export default async function CameraPage({ params, searchParams, }: CameraProps & PaginationParams) { - const camera = getCameraFromKey(params.camera); + const camera = getCameraFromParams(params); const { photos, diff --git a/src/app/shot-on/[camera]/share/page.tsx b/src/app/shot-on/[make]/[model]/share/page.tsx similarity index 87% rename from src/app/shot-on/[camera]/share/page.tsx rename to src/app/shot-on/[make]/[model]/share/page.tsx index f86568bb..060614d6 100644 --- a/src/app/shot-on/[camera]/share/page.tsx +++ b/src/app/shot-on/[make]/[model]/share/page.tsx @@ -1,4 +1,8 @@ -import { cameraFromPhoto, getCameraFromKey } from '@/camera'; +import { + CameraProps, + cameraFromPhoto, + getCameraFromParams, +} from '@/camera'; import CameraShareModal from '@/camera/CameraShareModal'; import { generateMetaForCamera } from '@/camera/meta'; import { Metadata } from 'next/types'; @@ -10,14 +14,10 @@ import { } from '@/camera/data'; import CameraOverview from '@/camera/CameraOverview'; -interface CameraProps { - params: { camera: string } -} - export async function generateMetadata({ params, }: CameraProps): Promise { - const camera = getCameraFromKey(params.camera); + const camera = getCameraFromParams(params); const [ photos, @@ -55,7 +55,7 @@ export default async function Share({ params, searchParams, }: CameraProps & PaginationParams) { - const cameraFromParams = getCameraFromKey(params.camera); + const cameraFromParams = getCameraFromParams(params); const { photos, diff --git a/src/camera/CameraHeader.tsx b/src/camera/CameraHeader.tsx index 657c0e5a..35f7b9ef 100644 --- a/src/camera/CameraHeader.tsx +++ b/src/camera/CameraHeader.tsx @@ -21,7 +21,7 @@ export default function CameraHeader({ const camera = cameraFromPhoto(photos[0], cameraProp); return ( } + entity={} entityVerb="Photo" entityDescription={ descriptionForCameraPhotos(photos, undefined, count, dateRange)} diff --git a/src/camera/PhotoCamera.tsx b/src/camera/PhotoCamera.tsx index ef2d9faa..c0bd744e 100644 --- a/src/camera/PhotoCamera.tsx +++ b/src/camera/PhotoCamera.tsx @@ -2,7 +2,9 @@ import { AiFillApple } from 'react-icons/ai'; import { pathForCamera } from '@/site/paths'; import { IoMdCamera } from 'react-icons/io'; import { Camera, formatCameraText } from '.'; -import EntityLink, { EntityLinkExternalProps } from '@/components/EntityLink'; +import EntityLink, { + EntityLinkExternalProps, +} from '@/components/primitives/EntityLink'; export default function PhotoCamera({ camera, @@ -27,12 +29,12 @@ export default function PhotoCamera({ icon={showAppleIcon ? : } type={showAppleIcon && isCameraApple ? 'icon-first' : type} badged={badged} diff --git a/src/camera/index.ts b/src/camera/index.ts index 8b609f9b..1a2e910a 100644 --- a/src/camera/index.ts +++ b/src/camera/index.ts @@ -8,6 +8,14 @@ export type Camera = { model: string }; +export interface CameraProps { + params: Camera +} + +export interface PhotoCameraProps { + params: Camera & { photoId: string } +} + export type CameraWithCount = { cameraKey: string camera: Camera @@ -19,11 +27,16 @@ export type Cameras = CameraWithCount[]; export const createCameraKey = ({ make, model }: Camera) => parameterize(`${make}-${model}`, true); -// Assumes no makes ('Fujifilm,' 'Apple,' 'Canon', etc.) have dashes -export const getCameraFromKey = (cameraKey: string): Camera => { - const [make, model] = cameraKey.toLowerCase().split(/[-| ](.*)/s); - return { make, model }; -}; +export const getCameraFromParams = ({ + make, + model, +}: { + make: string, + model: string, +}): Camera => ({ + make: parameterize(make, true), + model: parameterize(model, true), +}); export const sortCamerasWithCount = ( a: CameraWithCount, @@ -36,36 +49,34 @@ export const sortCamerasWithCount = ( export const cameraFromPhoto = ( photo: Photo | undefined, - fallback?: Camera | string, + fallback?: Camera, ): Camera => photo?.make && photo?.model ? { make: photo.make, model: photo.model } - : typeof fallback === 'string' - ? getCameraFromKey(fallback) - : fallback ?? CAMERA_PLACEHOLDER; + : fallback ?? CAMERA_PLACEHOLDER; export const formatCameraText = ( - { make, model: modelRaw }: Camera, - includeMakeApple?: boolean, + { make: makeRaw, model: modelRaw }: Camera, + includeMake: 'always' | 'never' | 'if-not-apple' = 'if-not-apple', + removeIPhoneOnLongModels?: boolean ) => { + // Remove 'Corporation' from makes like 'Nikon Corporation' + const make = makeRaw.replace(/ Corporation/i, ''); // Remove potential duplicate make from model - const model = modelRaw.replace(`${make} `, ''); - return make === 'Apple' && !includeMakeApple - ? model + let model = modelRaw.replace(`${make} `, ''); + if ( + removeIPhoneOnLongModels && + model.includes('iPhone') && + model.length > 9 + ) { + model = model.replace(/iPhone\s*/i, ''); + } + return ( + includeMake === 'never' || + includeMake === 'if-not-apple' && make === 'Apple' + ) ? model : `${make} ${model}`; }; -export const formatCameraModelText = ( - { make, model: modelRaw }: Camera, -) => { - // Remove potential duplicate make from model - const model = modelRaw.replace(`${make} `, ''); - const textLength = model?.length ?? 0; - if (textLength > 0 && textLength <= 8) { - return model; - } else if (model?.includes('iPhone')) { - return model.split('iPhone')[1]; - } else { - return undefined; - } -}; +export const formatCameraModelTextShort = (camera: Camera) => + formatCameraText(camera, 'never', true); diff --git a/src/components/Badge.tsx b/src/components/Badge.tsx index a1d60b27..3ca23de1 100644 --- a/src/components/Badge.tsx +++ b/src/components/Badge.tsx @@ -27,8 +27,9 @@ export default function Badge({ ); case 'small': return clsx( - 'px-[0.3rem] py-1 rounded-[0.25rem]', - 'text-[0.7rem] font-medium', + 'h-max-baseline', + 'px-[5px] py-[2.75px]', + 'text-[0.7rem] font-medium rounded-[0.25rem]', highContrast ? 'text-invert bg-invert' : 'text-medium bg-gray-300/30 dark:bg-gray-700/50', diff --git a/src/components/CommandKClient.tsx b/src/components/CommandKClient.tsx index 8b6a1d3d..f23e356f 100644 --- a/src/components/CommandKClient.tsx +++ b/src/components/CommandKClient.tsx @@ -19,6 +19,7 @@ import { BiDesktop, BiMoon, BiSun } from 'react-icons/bi'; import { IoInvertModeSharp } from 'react-icons/io5'; import { useAppState } from '@/state/AppState'; import { getPhotoItemsAction } from '@/photo/actions'; +import { RiToolsFill } from 'react-icons/ri'; const LISTENER_KEYDOWN = 'keydown'; const MINIMUM_QUERY_LENGTH = 2; @@ -38,16 +39,19 @@ export type CommandKSection = { } export default function CommandKClient({ - sections = [], + serverSections = [], + showDebugTools, footer, }: { - sections?: CommandKSection[] + serverSections?: CommandKSection[] + showDebugTools?: boolean footer?: string }) { const { isCommandKOpen: isOpen, setIsCommandKOpen: setIsOpen, setShouldRespondToKeyboardCommands, + setShouldShowBaselineGrid, } = useAppState(); const isOpenRef = useRef(isOpen); @@ -131,7 +135,7 @@ export default function CommandKClient({ } }, [isOpen, setShouldRespondToKeyboardCommands]); - const sectionTheme: CommandKSection = { + const clientSections: CommandKSection[] = [{ heading: 'Theme', accessory: , action: () => setTheme('dark'), }], - }; + }]; + + if (showDebugTools) { + clientSections.push({ + heading: 'Debug Tools', + accessory: , + items: [{ + label: 'Toggle Baseline Grid', + action: () => setShouldShowBaselineGrid?.(prev => !prev), + }], + }); + } return ( {queriedSections - .concat(sections) - .concat(sectionTheme) + .concat(serverSections) + .concat(clientSections) .filter(({ items }) => items.length > 0) .map(({ heading, accessory, items }) => + {children} +
+ ); +} diff --git a/src/components/EntityLink.tsx b/src/components/EntityLink.tsx deleted file mode 100644 index ae2f0955..00000000 --- a/src/components/EntityLink.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import Link from 'next/link'; -import { ReactNode } from 'react'; -import Badge from './Badge'; -import { clsx } from 'clsx/lite'; - -export interface EntityLinkExternalProps { - type?: 'icon-last' | 'icon-first' | 'icon-only' | 'text-only' - badged?: boolean - contrast?: 'low' | 'medium' | 'high' - prefetch?: boolean -} - -export default function EntityLink({ - label, - labelSmall, - href, - icon, - title, - type = 'icon-first', - badged, - prefetch, - contrast = 'high', - hoverEntity, -}: { - label: ReactNode - labelSmall?: ReactNode - href: string - icon?: ReactNode - title?: string - hoverEntity?: ReactNode -} & EntityLinkExternalProps) { - const renderLabel = () => <> - - {labelSmall ?? label} - - - {label} - - ; - - const classForContrast = () => { - switch (contrast) { - case 'low': - return 'text-dim'; - case 'high': - return 'text-main'; - default: - return 'text-medium'; - } - }; - - return ( - - - {type !== 'icon-only' && <> - {badged - ? - - {renderLabel()} - - - : - {renderLabel()} - } - } - {icon && type !== 'text-only' && - - {icon} - } - - {hoverEntity !== undefined && - - {hoverEntity} - } - - ); -} diff --git a/src/components/HeaderList.tsx b/src/components/HeaderList.tsx index 9abc7f98..31f80d33 100644 --- a/src/components/HeaderList.tsx +++ b/src/components/HeaderList.tsx @@ -17,7 +17,7 @@ export default function HeaderList({ - {formatDate(date)} - - ); -}; diff --git a/src/components/OGTile.tsx b/src/components/OGTile.tsx index 8afa1603..46938167 100644 --- a/src/components/OGTile.tsx +++ b/src/components/OGTile.tsx @@ -110,9 +110,8 @@ export default function OGTile({ />}
+ {/* Mobile */} + + {formatDate(date, true)} + + {/* Desktop */} + + {formatDate(date)} + + + ); +} diff --git a/src/components/ShareModal.tsx b/src/components/ShareModal.tsx index 9e3bad45..d807ed86 100644 --- a/src/components/ShareModal.tsx +++ b/src/components/ShareModal.tsx @@ -24,7 +24,7 @@ export default function ShareModal({
diff --git a/src/components/primitives/EntityLink.tsx b/src/components/primitives/EntityLink.tsx new file mode 100644 index 00000000..9ccd22fc --- /dev/null +++ b/src/components/primitives/EntityLink.tsx @@ -0,0 +1,89 @@ +import { ReactNode } from 'react'; +import LabeledIcon, { LabeledIconType } from './LabeledIcon'; +import Badge from '../Badge'; +import { clsx } from 'clsx/lite'; + +export interface EntityLinkExternalProps { + type?: LabeledIconType + badged?: boolean + contrast?: 'low' | 'medium' | 'high' +} + +export default function EntityLink({ + icon, + label, + labelSmall, + iconWide, + type, + badged, + contrast = 'medium', + href, + prefetch, + title, + hoverEntity, + debug, +}: { + icon: ReactNode + label: ReactNode + labelSmall?: ReactNode + iconWide?: boolean + href?: string + prefetch?: boolean + title?: string + hoverEntity?: ReactNode + debug?: boolean +} & EntityLinkExternalProps) { + const classForContrast = () => { + switch (contrast) { + case 'low': + return 'text-dim'; + case 'high': + return 'text-main'; + default: + return 'text-medium'; + } + }; + + const renderLabel = () => <> + + {labelSmall ?? label} + + + {label} + + ; + + return ( + + + {badged + ? + {renderLabel()} + + : renderLabel()} + + {hoverEntity !== undefined && + + {hoverEntity} + } + + ); +} diff --git a/src/components/primitives/Icon.tsx b/src/components/primitives/Icon.tsx new file mode 100644 index 00000000..57adbe06 --- /dev/null +++ b/src/components/primitives/Icon.tsx @@ -0,0 +1,35 @@ +import { ReactNode } from 'react'; +import { clsx } from 'clsx/lite'; +import Spinner from '../Spinner'; + +export default function Icon({ + children, + className, + iconClassName, + wide, + loading, + debug, +}: { + children: ReactNode + className?: string + iconClassName?: string + wide?: boolean + loading?: boolean + debug?: boolean, +}) { + return ( + + {loading + ? + : + {children} + } + + ); +} diff --git a/src/components/primitives/LabeledIcon.tsx b/src/components/primitives/LabeledIcon.tsx new file mode 100644 index 00000000..ca249680 --- /dev/null +++ b/src/components/primitives/LabeledIcon.tsx @@ -0,0 +1,60 @@ +import { ComponentProps, ReactNode } from 'react'; +import Icon from './Icon'; +import { clsx } from 'clsx/lite'; +import Link from 'next/link'; + +export type LabeledIconType = + 'icon-first' | + 'icon-last' | + 'icon-only' | + 'text-only'; + +export default function LabeledIcon({ + icon, + type = 'icon-first', + className: classNameProp, + children, + iconWide, + href, + prefetch, + debug, +}: { + icon?: ReactNode, + type?: LabeledIconType, + className?: string, + children: ReactNode, + iconWide?:boolean, + debug?: boolean, +} & Partial>) { + const className = clsx( + 'inline-flex gap-x-1 md:gap-x-1.5', + classNameProp, + debug && 'border border-green-500 m-[-1px]', + ); + + const renderContent = () => <> + {icon && type !== 'text-only' && + + {icon} + } + {children && type !== 'icon-only' && + + {children} + } + ; + + return href + ? + {renderContent()} + + :
+ {renderContent()} +
; +} diff --git a/src/image-response/PhotoImageResponse.tsx b/src/image-response/PhotoImageResponse.tsx index be8e7796..f633e513 100644 --- a/src/image-response/PhotoImageResponse.tsx +++ b/src/image-response/PhotoImageResponse.tsx @@ -5,7 +5,7 @@ import ImagePhotoGrid from './components/ImagePhotoGrid'; import ImageContainer from './components/ImageContainer'; import { OG_TEXT_BOTTOM_ALIGNMENT } from '@/site/config'; import { NextImageSize } from '@/services/next-image'; -import { cameraFromPhoto, formatCameraModelText } from '@/camera'; +import { cameraFromPhoto, formatCameraModelTextShort } from '@/camera'; export default function PhotoImageResponse({ photo, @@ -19,7 +19,7 @@ export default function PhotoImageResponse({ fontFamily: string }) { const model = photo.model - ? formatCameraModelText(cameraFromPhoto(photo)) + ? formatCameraModelTextShort(cameraFromPhoto(photo)) : undefined; return ( diff --git a/src/photo/PhotoDate.tsx b/src/photo/PhotoDate.tsx new file mode 100644 index 00000000..c2a9ea24 --- /dev/null +++ b/src/photo/PhotoDate.tsx @@ -0,0 +1,17 @@ +import ResponsiveDate from '@/components/ResponsiveDate'; +import { Photo } from '.'; +import { useMemo } from 'react'; + +export default function PhotoDate({ + photo: { takenAtNaive }, +}: { + photo: Photo +}) { + const date = useMemo(() => { + const date = new Date(takenAtNaive); + return isNaN(date.getTime()) ? new Date() : date; + }, [takenAtNaive]); + return ( + + ); +} diff --git a/src/photo/PhotoLarge.tsx b/src/photo/PhotoLarge.tsx index 4beb3c2d..c73984bb 100644 --- a/src/photo/PhotoLarge.tsx +++ b/src/photo/PhotoLarge.tsx @@ -18,6 +18,7 @@ import PhotoFilmSimulation from '@/simulation/PhotoFilmSimulation'; import { sortTags } from '@/tag'; import AdminPhotoMenu from '@/admin/AdminPhotoMenu'; import { Suspense } from 'react'; +import DivDebugBaselineGrid from '@/components/DivDebugBaselineGrid'; export default function PhotoLarge({ photo, @@ -70,12 +71,11 @@ export default function PhotoLarge({ /> } contentSide={ -
{/* Meta */} @@ -95,7 +95,7 @@ export default function PhotoLarge({
-
+
{photo.caption &&
{photo.caption} @@ -113,7 +113,7 @@ export default function PhotoLarge({
{/* EXIF Data */} -
+
{showExifContent && <>
    @@ -141,8 +141,8 @@ export default function PhotoLarge({ />} }
    -
} + } /> ); }; diff --git a/src/photo/PhotoSetHeader.tsx b/src/photo/PhotoSetHeader.tsx index 163a8a9f..7fe89136 100644 --- a/src/photo/PhotoSetHeader.tsx +++ b/src/photo/PhotoSetHeader.tsx @@ -4,6 +4,7 @@ import ShareButton from '@/components/ShareButton'; import AnimateItems from '@/components/AnimateItems'; import { ReactNode } from 'react'; import { HIGH_DENSITY_GRID } from '@/site/config'; +import DivDebugBaselineGrid from '@/components/DivDebugBaselineGrid'; export default function PhotoSetHeader({ entity, @@ -35,7 +36,7 @@ export default function PhotoSetHeader({ type="bottom" distanceOffset={10} animateOnFirstLoadOnly - items={[
{end}
– {start}} -
]} + ]} /> ); } diff --git a/src/photo/actions.tsx b/src/photo/actions.tsx index 23359879..cc527dfa 100644 --- a/src/photo/actions.tsx +++ b/src/photo/actions.tsx @@ -36,7 +36,6 @@ import { extractExifDataFromBlobPath } from './server'; import { TAG_FAVS, isTagFavs } from '@/tag'; import { TbPhoto } from 'react-icons/tb'; import PhotoTiny from './PhotoTiny'; -import { formatDate } from '@/utility/date'; import { convertPhotoToPhotoDbInsert, getKeywordsForPhoto, @@ -45,6 +44,7 @@ import { import { safelyRunAdminServerAction } from '@/auth'; import { AI_IMAGE_QUERIES, AiImageQuery } from './ai'; import { streamOpenAiImageQuery } from '@/services/openai'; +import PhotoDate from './PhotoDate'; export async function createPhotoAction(formData: FormData) { return safelyRunAdminServerAction(async () => { @@ -211,14 +211,7 @@ export async function getPhotoItemsAction(query: string) { items: photos.map(photo => ({ label: titleForPhoto(photo), keywords: getKeywordsForPhoto(photo), - annotation: <> - - {formatDate(photo.takenAt)} - - - {formatDate(photo.takenAt, true)} - - , + annotation: , accessory: , path: pathForPhoto(photo), })), diff --git a/src/photo/ai/AiButton.tsx b/src/photo/ai/AiButton.tsx index b7892312..b88d1fb0 100644 --- a/src/photo/ai/AiButton.tsx +++ b/src/photo/ai/AiButton.tsx @@ -41,6 +41,7 @@ export default function AiButton({ return (