diff --git a/src/admin/AdminPhotoMenuClient.tsx b/src/admin/AdminPhotoMenuClient.tsx
index dc0ceda6..e73f0629 100644
--- a/src/admin/AdminPhotoMenuClient.tsx
+++ b/src/admin/AdminPhotoMenuClient.tsx
@@ -4,7 +4,11 @@ import { ComponentProps, useMemo } from 'react';
import { pathForAdminPhotoEdit, pathForPhoto } from '@/site/paths';
import { deletePhotoAction, toggleFavoritePhotoAction } from '@/photo/actions';
import { FaRegEdit, FaRegStar, FaStar } from 'react-icons/fa';
-import { Photo, deleteConfirmationTextForPhoto } from '@/photo';
+import {
+ Photo,
+ deleteConfirmationTextForPhoto,
+ downloadFileNameForPhoto,
+} from '@/photo';
import { isPathFavs, isPhotoFav } from '@/tag';
import { usePathname } from 'next/navigation';
import { BiTrash } from 'react-icons/bi';
@@ -64,7 +68,7 @@ export default function AdminPhotoMenuClient({
className="translate-x-[-1.5px] translate-y-[-0.5px]"
/>,
href: photo.url,
- hrefDownloadName: photo.url.split('/').pop(),
+ hrefDownloadName: downloadFileNameForPhoto(photo),
});
items.push({
label: 'Delete',
diff --git a/src/components/DownloadButton.tsx b/src/components/DownloadButton.tsx
index 8219bf4e..e7b88b8f 100644
--- a/src/components/DownloadButton.tsx
+++ b/src/components/DownloadButton.tsx
@@ -1,7 +1,8 @@
import { MdOutlineFileDownload } from 'react-icons/md';
-import PathLoaderButton from './primitives/PathLoaderButton';
import { clsx } from 'clsx/lite';
-import { Photo } from '@/photo';
+import { downloadFileNameForPhoto, Photo } from '@/photo';
+import LoaderButton from './primitives/LoaderButton';
+import { useState } from 'react';
export default function DownloadButton({
photo,
@@ -12,30 +13,30 @@ export default function DownloadButton({
dim?: boolean
className?: string
}) {
- const {url, title} = photo;
+ const [isLoading, setIsLoading] = useState(false);
return (
- }
+ icon={}
spinnerColor='dim'
styleAs='link'
- shouldReplace
- handleAction={async () => {
- const response = await fetch(url);
- const blob = await response.blob();
+ isLoading={isLoading}
+ onClick={async () => {
+ setIsLoading(true);
+ const blob = await fetch(photo.url)
+ .then(response => response.blob())
+ .finally(() => setIsLoading(false));
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
- link.download = title
- ? title.replace(/[^a-z0-9]/gi, '_').toLowerCase()
- : url.split('/').pop() || 'download';
+ link.download = downloadFileNameForPhoto(photo);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
diff --git a/src/components/primitives/PathLoaderButton.tsx b/src/components/primitives/PathLoaderButton.tsx
index 79c1af06..27a4efa8 100644
--- a/src/components/primitives/PathLoaderButton.tsx
+++ b/src/components/primitives/PathLoaderButton.tsx
@@ -10,7 +10,6 @@ export default function PathLoaderButton({
loaderDelay = 100,
shouldScroll = true,
shouldReplace,
- handleAction,
children,
...props
}: {
@@ -19,7 +18,6 @@ export default function PathLoaderButton({
loaderDelay?: number
shouldScroll?: boolean
shouldReplace?: boolean
- handleAction?: () => Promise
} & ComponentProps) {
const router = useRouter();
@@ -48,15 +46,11 @@ export default function PathLoaderButton({
{
- startTransition(async () => {
- if (handleAction) {
- await handleAction();
+ startTransition(() => {
+ if (shouldReplace) {
+ router.replace(path, { scroll: shouldScroll });
} else {
- if (shouldReplace) {
- router.replace(path, { scroll: shouldScroll });
- } else {
- router.push(path, { scroll: shouldScroll });
- }
+ router.push(path, { scroll: shouldScroll });
}
});
}}
diff --git a/src/photo/index.ts b/src/photo/index.ts
index e3890a20..97c96b71 100644
--- a/src/photo/index.ts
+++ b/src/photo/index.ts
@@ -12,6 +12,7 @@ import {
formatExposureCompensation,
formatExposureTime,
} from '@/utility/exif';
+import { parameterize } from '@/utility/string';
import camelcaseKeys from 'camelcase-keys';
import { isBefore } from 'date-fns';
import type { Metadata } from 'next';
@@ -311,5 +312,10 @@ export const isNextImageReadyBasedOnPhotos = async (photos: Photo[]) =>
.then(response => response.ok)
.catch(() => false);
+export const downloadFileNameForPhoto = (photo: Photo) =>
+ photo.title
+ ? `${parameterize(photo.title)}.${photo.extension}`
+ : photo.url.split('/').pop() || 'download';
+
export const doesPhotoNeedBlurCompatibility = (photo: Photo) =>
isBefore(photo.updatedAt, new Date('2024-05-07'));