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'));