Refine file download behavior

This commit is contained in:
Sam Becker 2024-09-21 15:26:11 -05:00
parent 0dc627b774
commit d3c8db474e
4 changed files with 30 additions and 25 deletions

View File

@ -4,7 +4,11 @@ import { ComponentProps, useMemo } from 'react';
import { pathForAdminPhotoEdit, pathForPhoto } from '@/site/paths'; import { pathForAdminPhotoEdit, pathForPhoto } from '@/site/paths';
import { deletePhotoAction, toggleFavoritePhotoAction } from '@/photo/actions'; import { deletePhotoAction, toggleFavoritePhotoAction } from '@/photo/actions';
import { FaRegEdit, FaRegStar, FaStar } from 'react-icons/fa'; 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 { isPathFavs, isPhotoFav } from '@/tag';
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';
import { BiTrash } from 'react-icons/bi'; import { BiTrash } from 'react-icons/bi';
@ -64,7 +68,7 @@ export default function AdminPhotoMenuClient({
className="translate-x-[-1.5px] translate-y-[-0.5px]" className="translate-x-[-1.5px] translate-y-[-0.5px]"
/>, />,
href: photo.url, href: photo.url,
hrefDownloadName: photo.url.split('/').pop(), hrefDownloadName: downloadFileNameForPhoto(photo),
}); });
items.push({ items.push({
label: 'Delete', label: 'Delete',

View File

@ -1,7 +1,8 @@
import { MdOutlineFileDownload } from 'react-icons/md'; import { MdOutlineFileDownload } from 'react-icons/md';
import PathLoaderButton from './primitives/PathLoaderButton';
import { clsx } from 'clsx/lite'; 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({ export default function DownloadButton({
photo, photo,
@ -12,30 +13,30 @@ export default function DownloadButton({
dim?: boolean dim?: boolean
className?: string className?: string
}) { }) {
const {url, title} = photo; const [isLoading, setIsLoading] = useState(false);
return ( return (
<PathLoaderButton <LoaderButton
path={url} title="Download Original File"
className={clsx( className={clsx(
className, className,
dim ? 'text-dim' : 'text-medium', dim ? 'text-dim' : 'text-medium',
'-mx-0.5 translate-x-0.5', '-mx-0.5 translate-x-0.5',
'sm:mx-0 sm:translate-x-0' 'sm:mx-0 sm:translate-x-0'
)} )}
icon={<MdOutlineFileDownload size={16} />} icon={<MdOutlineFileDownload size={18} />}
spinnerColor='dim' spinnerColor='dim'
styleAs='link' styleAs='link'
shouldReplace isLoading={isLoading}
handleAction={async () => { onClick={async () => {
const response = await fetch(url); setIsLoading(true);
const blob = await response.blob(); const blob = await fetch(photo.url)
.then(response => response.blob())
.finally(() => setIsLoading(false));
const downloadUrl = window.URL.createObjectURL(blob); const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a'); const link = document.createElement('a');
link.href = downloadUrl; link.href = downloadUrl;
link.download = title link.download = downloadFileNameForPhoto(photo);
? title.replace(/[^a-z0-9]/gi, '_').toLowerCase()
: url.split('/').pop() || 'download';
document.body.appendChild(link); document.body.appendChild(link);
link.click(); link.click();
document.body.removeChild(link); document.body.removeChild(link);

View File

@ -10,7 +10,6 @@ export default function PathLoaderButton({
loaderDelay = 100, loaderDelay = 100,
shouldScroll = true, shouldScroll = true,
shouldReplace, shouldReplace,
handleAction,
children, children,
...props ...props
}: { }: {
@ -19,7 +18,6 @@ export default function PathLoaderButton({
loaderDelay?: number loaderDelay?: number
shouldScroll?: boolean shouldScroll?: boolean
shouldReplace?: boolean shouldReplace?: boolean
handleAction?: () => Promise<void>
} & ComponentProps<typeof LoaderButton>) { } & ComponentProps<typeof LoaderButton>) {
const router = useRouter(); const router = useRouter();
@ -48,15 +46,11 @@ export default function PathLoaderButton({
<LoaderButton <LoaderButton
{...props} {...props}
onClick={() => { onClick={() => {
startTransition(async () => { startTransition(() => {
if (handleAction) { if (shouldReplace) {
await handleAction(); router.replace(path, { scroll: shouldScroll });
} else { } else {
if (shouldReplace) { router.push(path, { scroll: shouldScroll });
router.replace(path, { scroll: shouldScroll });
} else {
router.push(path, { scroll: shouldScroll });
}
} }
}); });
}} }}

View File

@ -12,6 +12,7 @@ import {
formatExposureCompensation, formatExposureCompensation,
formatExposureTime, formatExposureTime,
} from '@/utility/exif'; } from '@/utility/exif';
import { parameterize } from '@/utility/string';
import camelcaseKeys from 'camelcase-keys'; import camelcaseKeys from 'camelcase-keys';
import { isBefore } from 'date-fns'; import { isBefore } from 'date-fns';
import type { Metadata } from 'next'; import type { Metadata } from 'next';
@ -311,5 +312,10 @@ export const isNextImageReadyBasedOnPhotos = async (photos: Photo[]) =>
.then(response => response.ok) .then(response => response.ok)
.catch(() => false); .catch(() => false);
export const downloadFileNameForPhoto = (photo: Photo) =>
photo.title
? `${parameterize(photo.title)}.${photo.extension}`
: photo.url.split('/').pop() || 'download';
export const doesPhotoNeedBlurCompatibility = (photo: Photo) => export const doesPhotoNeedBlurCompatibility = (photo: Photo) =>
isBefore(photo.updatedAt, new Date('2024-05-07')); isBefore(photo.updatedAt, new Date('2024-05-07'));