Accept multiple files when uploading
This commit is contained in:
parent
f200d1d754
commit
8bef969908
@ -19,7 +19,12 @@ export default function ImageInput({
|
|||||||
debug,
|
debug,
|
||||||
}: {
|
}: {
|
||||||
onStart?: () => void
|
onStart?: () => void
|
||||||
onBlobReady?: (blob: Blob, extension?: string) => void
|
onBlobReady?: (args: {
|
||||||
|
blob: Blob,
|
||||||
|
extension?: string,
|
||||||
|
hasMultipleUploads?: boolean,
|
||||||
|
isLastBlob?: boolean,
|
||||||
|
}) => Promise<any>
|
||||||
maxSize?: number
|
maxSize?: number
|
||||||
quality?: number
|
quality?: number
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
@ -27,7 +32,7 @@ export default function ImageInput({
|
|||||||
}) {
|
}) {
|
||||||
const ref = useRef<HTMLCanvasElement>(null);
|
const ref = useRef<HTMLCanvasElement>(null);
|
||||||
|
|
||||||
const [fileName, setFileName] = useState<string>();
|
const [statusText, setStatusText] = useState<string>();
|
||||||
const [image, setImage] = useState<HTMLImageElement>();
|
const [image, setImage] = useState<HTMLImageElement>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -63,62 +68,84 @@ export default function ImageInput({
|
|||||||
className="!hidden"
|
className="!hidden"
|
||||||
accept={ACCEPTED_PHOTO_FILE_TYPES.join(',')}
|
accept={ACCEPTED_PHOTO_FILE_TYPES.join(',')}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
|
multiple
|
||||||
onChange={async e => {
|
onChange={async e => {
|
||||||
onStart?.();
|
onStart?.();
|
||||||
const file = e.currentTarget.files?.[0];
|
const { files } = e.currentTarget;
|
||||||
setFileName(file?.name);
|
if (files && files.length > 0) {
|
||||||
const extension = file?.name.split('.').pop()?.toLowerCase();
|
for (let i = 0; i < files?.length; i++) {
|
||||||
const canvas = ref.current;
|
const file = files[i];
|
||||||
if (file) {
|
if (file) {
|
||||||
if (!(maxSize && canvas)) {
|
const callbackArgs = {
|
||||||
// No need to process
|
extension: file.name.split('.').pop()?.toLowerCase(),
|
||||||
onBlobReady?.(file);
|
hasMultipleUploads: files.length > 1,
|
||||||
} else {
|
isLastBlob: i === files.length - 1,
|
||||||
// Process images that need resizing
|
};
|
||||||
const image = await blobToImage(file);
|
if (files.length > 1) {
|
||||||
setImage(image);
|
setStatusText(
|
||||||
const { naturalWidth, naturalHeight } = image;
|
`Uploading ${i + 1} of ${files.length}: ${file.name}`
|
||||||
const ratio = naturalWidth / naturalHeight;
|
);
|
||||||
|
} else {
|
||||||
const width =
|
setStatusText(`Uploading ${file.name}`);
|
||||||
Math.round(ratio >= 1 ? maxSize : maxSize * ratio);
|
}
|
||||||
const height =
|
const canvas = ref.current;
|
||||||
Math.round(ratio >= 1 ? maxSize / ratio : maxSize);
|
if (!(maxSize && canvas)) {
|
||||||
|
// No need to process
|
||||||
canvas.width = width;
|
await onBlobReady?.({
|
||||||
canvas.height = height;
|
...callbackArgs,
|
||||||
|
blob: file,
|
||||||
// Specify wide gamut to avoid data loss while resizing
|
});
|
||||||
const ctx = canvas.getContext(
|
} else {
|
||||||
'2d',
|
// Process images that need resizing
|
||||||
{ colorSpace: 'display-p3' },
|
const image = await blobToImage(file);
|
||||||
);
|
setImage(image);
|
||||||
|
const { naturalWidth, naturalHeight } = image;
|
||||||
ctx?.drawImage(
|
const ratio = naturalWidth / naturalHeight;
|
||||||
image,
|
|
||||||
0,
|
const width =
|
||||||
0,
|
Math.round(ratio >= 1 ? maxSize : maxSize * ratio);
|
||||||
canvas.width,
|
const height =
|
||||||
canvas.height,
|
Math.round(ratio >= 1 ? maxSize / ratio : maxSize);
|
||||||
);
|
|
||||||
canvas.toBlob(
|
canvas.width = width;
|
||||||
async blob => {
|
canvas.height = height;
|
||||||
if (blob) {
|
|
||||||
const blobWithExif = await CopyExif(file, blob);
|
// Specify wide gamut to avoid data loss while resizing
|
||||||
onBlobReady?.(blobWithExif, extension);
|
const ctx = canvas.getContext(
|
||||||
}
|
'2d',
|
||||||
},
|
{ colorSpace: 'display-p3' },
|
||||||
'image/jpeg',
|
);
|
||||||
quality,
|
|
||||||
);
|
ctx?.drawImage(
|
||||||
|
image,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
canvas.width,
|
||||||
|
canvas.height,
|
||||||
|
);
|
||||||
|
canvas.toBlob(
|
||||||
|
async blob => {
|
||||||
|
if (blob) {
|
||||||
|
const blobWithExif = await CopyExif(file, blob);
|
||||||
|
await onBlobReady?.({
|
||||||
|
...callbackArgs,
|
||||||
|
blob: blobWithExif,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'image/jpeg',
|
||||||
|
quality,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
{fileName &&
|
{statusText &&
|
||||||
<div className="max-w-full truncate text-ellipsis">
|
<div className="max-w-full truncate text-ellipsis">
|
||||||
{fileName}
|
{statusText}
|
||||||
</div>}
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
<canvas
|
<canvas
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { uploadPhotoFromClient } from '@/services/blob';
|
import { uploadPhotoFromClient } from '@/services/blob';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { pathForAdminUploadUrl } from '@/site/paths';
|
import { PATH_ADMIN_UPLOADS, pathForAdminUploadUrl } from '@/site/paths';
|
||||||
import ImageInput from '../components/ImageInput';
|
import ImageInput from '../components/ImageInput';
|
||||||
import { MAX_IMAGE_SIZE } from '@/utility/image';
|
import { MAX_IMAGE_SIZE } from '@/utility/image';
|
||||||
import { cc } from '@/utility/css';
|
import { cc } from '@/utility/css';
|
||||||
@ -38,7 +38,12 @@ export default function PhotoUpload({
|
|||||||
setIsUploading(true);
|
setIsUploading(true);
|
||||||
setUploadError('');
|
setUploadError('');
|
||||||
}}
|
}}
|
||||||
onBlobReady={(blob, extension) => {
|
onBlobReady={async ({
|
||||||
|
blob,
|
||||||
|
extension,
|
||||||
|
hasMultipleUploads,
|
||||||
|
isLastBlob,
|
||||||
|
}) => {
|
||||||
if (debug) {
|
if (debug) {
|
||||||
setDebugDownload({
|
setDebugDownload({
|
||||||
href: URL.createObjectURL(blob),
|
href: URL.createObjectURL(blob),
|
||||||
@ -47,16 +52,23 @@ export default function PhotoUpload({
|
|||||||
setIsUploading(false);
|
setIsUploading(false);
|
||||||
setUploadError('');
|
setUploadError('');
|
||||||
} else {
|
} else {
|
||||||
uploadPhotoFromClient(
|
return uploadPhotoFromClient(
|
||||||
blob,
|
blob,
|
||||||
extension,
|
extension,
|
||||||
)
|
)
|
||||||
.then(({ url }) => {
|
.then(({ url }) => {
|
||||||
// Refresh page to update upload list,
|
if (isLastBlob) {
|
||||||
// relevant only when a photo isn't added
|
// Refresh page to update upload list,
|
||||||
router.refresh();
|
// relevant to upload count in nav
|
||||||
// Redirect to photo detail page
|
router.refresh();
|
||||||
router.push(pathForAdminUploadUrl(url));
|
if (hasMultipleUploads) {
|
||||||
|
// Redirect to view multiple uploads
|
||||||
|
router.push(PATH_ADMIN_UPLOADS);
|
||||||
|
} else {
|
||||||
|
// Redirect to photo detail page
|
||||||
|
router.push(pathForAdminUploadUrl(url));
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
setIsUploading(false);
|
setIsUploading(false);
|
||||||
|
|||||||
@ -31,8 +31,7 @@ const PATH_CAMERA_DYNAMIC = `${PREFIX_CAMERA}/:camera`;
|
|||||||
export const PATH_ADMIN_PHOTOS = `${PATH_ADMIN}/photos`;
|
export const PATH_ADMIN_PHOTOS = `${PATH_ADMIN}/photos`;
|
||||||
export const PATH_ADMIN_UPLOADS = `${PATH_ADMIN}/uploads`;
|
export const PATH_ADMIN_UPLOADS = `${PATH_ADMIN}/uploads`;
|
||||||
export const PATH_ADMIN_TAGS = `${PATH_ADMIN}/tags`;
|
export const PATH_ADMIN_TAGS = `${PATH_ADMIN}/tags`;
|
||||||
export const PATH_ADMIN_UPLOAD = `${PATH_ADMIN}/uploads`;
|
export const PATH_ADMIN_UPLOAD_BLOB = `${PATH_ADMIN_UPLOADS}/blob`;
|
||||||
export const PATH_ADMIN_UPLOAD_BLOB = `${PATH_ADMIN_UPLOAD}/blob`;
|
|
||||||
export const PATH_ADMIN_CONFIGURATION = `${PATH_ADMIN}/configuration`;
|
export const PATH_ADMIN_CONFIGURATION = `${PATH_ADMIN}/configuration`;
|
||||||
|
|
||||||
// Modifiers
|
// Modifiers
|
||||||
@ -45,7 +44,6 @@ export const PATHS_ADMIN = [
|
|||||||
PATH_ADMIN_PHOTOS,
|
PATH_ADMIN_PHOTOS,
|
||||||
PATH_ADMIN_UPLOADS,
|
PATH_ADMIN_UPLOADS,
|
||||||
PATH_ADMIN_TAGS,
|
PATH_ADMIN_TAGS,
|
||||||
PATH_ADMIN_UPLOAD,
|
|
||||||
PATH_ADMIN_UPLOAD_BLOB,
|
PATH_ADMIN_UPLOAD_BLOB,
|
||||||
PATH_ADMIN_CONFIGURATION,
|
PATH_ADMIN_CONFIGURATION,
|
||||||
];
|
];
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user