Merge branch 'main' into static

This commit is contained in:
Sam Becker 2024-04-01 09:02:08 -05:00
commit 9e024e048b
6 changed files with 399 additions and 345 deletions

View File

@ -18,18 +18,18 @@
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^14.2.2",
"@types/jest": "^29.5.12",
"@types/node": "^20.11.30",
"@types/react": "18.2.69",
"@types/react-dom": "18.2.22",
"@typescript-eslint/eslint-plugin": "^7.3.1",
"@typescript-eslint/parser": "^7.3.1",
"@types/node": "^20.12.2",
"@types/react": "18.2.73",
"@types/react-dom": "18.2.23",
"@typescript-eslint/eslint-plugin": "^7.4.0",
"@typescript-eslint/parser": "^7.4.0",
"@upstash/ratelimit": "^1.0.1",
"@vercel/analytics": "^1.2.2",
"@vercel/blob": "^0.22.1",
"@vercel/kv": "^1.0.1",
"@vercel/postgres": "0.7.2",
"@vercel/speed-insights": "^1.0.10",
"ai": "^3.0.13",
"ai": "^3.0.16",
"autoprefixer": "10.4.19",
"camelcase-keys": "^9.1.3",
"clsx": "^2.1.0",
@ -38,20 +38,20 @@
"eslint": "8.57.0",
"eslint-config-next": "14.1.4",
"exifr": "^7.1.3",
"framer-motion": "^11.0.20",
"framer-motion": "^11.0.24",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"nanoid": "^5.0.6",
"next": "14.2.0-canary.49",
"next": "14.2.0-canary.51",
"next-auth": "5.0.0-beta.15",
"next-themes": "^0.3.0",
"openai": "^4.29.2",
"openai": "^4.31.0",
"postcss": "8.4.38",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "^5.0.1",
"sonner": "^1.4.41",
"tailwindcss": "3.4.1",
"tailwindcss": "3.4.3",
"ts-exif-parser": "^0.2.2",
"typescript": "5.4.3",
"use-debounce": "^10.0.0"

632
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,7 @@ import PhotoLightbox from '@/photo/PhotoLightbox';
import FavsTag from '@/tag/FavsTag';
import { isTagFavs } from '@/tag';
import { getPhotosTagMeta } from '@/services/vercel-postgres';
import { clsx } from 'clsx/lite';
const MAX_PHOTO_TO_SHOW = 6;
@ -35,14 +36,14 @@ export default async function PhotoPageEdit({
<AdminChildPage
backPath={PATH_ADMIN_TAGS}
backLabel="Tags"
breadcrumb={<div className="flex items-center gap-2">
breadcrumb={<div className={clsx(
'flex items-center gap-2',
// Fix nested EntityLink-in-Badge quirk for tags
'[&>*>*:first-child]:items-center',
)}>
{isTagFavs(tag)
? <div className="[&>*>*>*>svg]:translate-y-[0.5px]">
<FavsTag />
</div>
: <div className="[&>*>*>*>svg]:translate-y-[1.5px]">
<PhotoTag {...{ tag }} />
</div>}
? <FavsTag />
: <PhotoTag {...{ tag }} />}
<div className="text-dim uppercase">
<span>{count}</span>
<span className="hidden xs:inline-block">

View File

@ -300,6 +300,7 @@ export default function TagInput({
}
tabIndex={0}
className={clsx(
'text-base',
'group flex items-center gap-1',
'cursor-pointer select-none',
'px-1.5 py-1 rounded-sm',

View File

@ -28,6 +28,9 @@ import { TagsWithMeta, sortTagsObjectWithoutFavs } from '@/tag';
import { formatCount, formatCountDescriptive } from '@/utility/string';
import { AiContent } from '../ai/useAiImageQueries';
import AiButton from '../ai/AiButton';
import Spinner from '@/components/Spinner';
import { getNextImageUrlForRequest } from '@/services/next-image';
import useDelay from '@/utility/useDelay';
const THUMBNAIL_SIZE = 300;
@ -59,6 +62,18 @@ export default function PhotoForm({
useState(getFormErrors(initialPhotoForm));
const [blurError, setBlurError] =
useState<string>();
const [hasBlurData, setHasBlurData] = useState(false);
const didLoad1000msAgo = useDelay(1000);
// Show image loading status when necessary for
// blur data or AI analysis
const showImageLoadingStatus =
!hasBlurData &&
didLoad1000msAgo && (
(BLUR_ENABLED && !formData.blurData) ||
aiContent !== undefined
);
// Update form when EXIF data
// is refreshed by parent
@ -122,6 +137,7 @@ export default function PhotoForm({
blurData,
}));
}
setHasBlurData(true);
}, []);
useEffect(() =>
@ -208,19 +224,43 @@ export default function PhotoForm({
{blurError}
</div>}
<div className="flex gap-2">
<ImageBlurFallback
alt="Upload"
src={url}
className={clsx(
'border rounded-md overflow-hidden',
'border-gray-200 dark:border-gray-700'
)}
width={width}
height={height}
priority
/>
<div className="relative">
<ImageBlurFallback
alt="Upload"
src={url}
className={clsx(
'border rounded-md overflow-hidden',
'border-gray-200 dark:border-gray-700',
)}
width={width}
height={height}
priority
/>
<div className={clsx(
'absolute top-2 left-2 transition-opacity duration-500',
showImageLoadingStatus ? 'opacity-100' : 'opacity-0',
)}>
<div className={clsx(
'leading-none text-xs font-medium uppercase tracking-wide',
'px-1.5 py-1 rounded-[4px]',
'inline-flex items-center gap-2',
'bg-white/70 dark:bg-black/60 backdrop-blur-md',
'border border-gray-900/10 dark:border-gray-700/70',
)}>
<Spinner
color="text"
size={9}
className={clsx(
'text-extra-dim',
'translate-x-[1px] translate-y-[0.5px]'
)}
/>
Analyzing image
</div>
</div>
</div>
<CanvasBlurCapture
imageUrl={url}
imageUrl={getNextImageUrlForRequest(url, 640)}
width={width}
height={height}
onLoad={aiContent?.setImageData}

12
src/utility/useDelay.ts Normal file
View File

@ -0,0 +1,12 @@
import { useEffect, useState } from 'react';
export default function useDelay(delay = 0) {
const [didLoad, setDidLoad] = useState(false);
useEffect(() => {
const timeout = setTimeout(() => setDidLoad(true), delay);
return () => clearTimeout(timeout);
}, [delay]);
return didLoad;
};