Merge branch 'main' into static
This commit is contained in:
commit
9e024e048b
20
package.json
20
package.json
@ -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
632
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -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">
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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
12
src/utility/useDelay.ts
Normal 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;
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user