diff --git a/src/admin/AdminAppMenu.tsx b/src/admin/AdminAppMenu.tsx index 0fe2dea7..4c2e5b05 100644 --- a/src/admin/AdminAppMenu.tsx +++ b/src/admin/AdminAppMenu.tsx @@ -34,6 +34,7 @@ export default function AdminAppMenu({ uploadsCount, tagsCount, selectedPhotoIds, + startUpload, setSelectedPhotoIds, refreshAdminData, clearAuthStateAndRedirect, @@ -47,6 +48,7 @@ export default function AdminAppMenu({ size={15} className="translate-x-[0.5px] translate-y-[0.5px]" />, + action: startUpload, }, { label: 'Manage Photos', ...photosCount !== undefined && { diff --git a/src/admin/AdminPhotosClient.tsx b/src/admin/AdminPhotosClient.tsx index cd04724a..d76317de 100644 --- a/src/admin/AdminPhotosClient.tsx +++ b/src/admin/AdminPhotosClient.tsx @@ -43,7 +43,7 @@ export default function AdminPhotosClient({ -
+
Promise + debug?: boolean +}) { + const { + uploadInputRef, + uploadState: { + isUploading, + filesLength, + fileUploadIndex, + fileUploadName, + uploadError, + debugDownload, + hideUploadPanel, + }, + setUploadState, + resetUploadState, + } = useAppState(); + + const router = useRouter(); + + const shouldResetUploadStateAfterPending = useRef(false); + const [isPending, startTransition] = useTransition(); + useEffect(() => { + if (!isPending) { + if (shouldResetUploadStateAfterPending.current) { + resetUploadState?.(); + shouldResetUploadStateAfterPending.current = false; + } + } + }, [isPending, resetUploadState]); + const isFinalizing = isPending && + shouldResetUploadStateAfterPending.current; return ( - -
-
- {isUploading - ?
- - {/* eslint-disable-next-line max-len */} - Uploading {fileUploadIndex + 1} of {filesLength}: {fileUploadName} - - -
- : 'Upload Photos'} + +
+
+ { + setUploadState?.({ + isUploading: true, + uploadError: '', + }); + }} + onBlobReady={async ({ + blob, + extension, + hasMultipleUploads, + isLastBlob, + }) => { + if (debug) { + setUploadState?.({ + isUploading: false, + uploadError: '', + debugDownload: { + href: URL.createObjectURL(blob), + fileName: `debug.${extension}`, + }, + }); + } else { + return uploadPhotoFromClient( + blob, + extension, + ) + .then(async url => { + if (isLastBlob) { + await onLastUpload?.(); + shouldResetUploadStateAfterPending.current = true; + startTransition(() => hasMultipleUploads + ? router.push(PATH_ADMIN_UPLOADS) + : router.push(pathForAdminUploadUrl(url))); + } + }) + .catch(error => { + setUploadState?.({ + isUploading: false, + uploadError: `Upload Error: ${error.message}`, + }); + }); + } + }} + showUploadStatus={false} + showUploadButton={false} + /> + {isUploading + ?
+ {isFinalizing + ? + Finishing + + : + {/* eslint-disable-next-line max-len */} + Uploading {fileUploadIndex + 1} of {filesLength}: {fileUploadName} + } + +
+ : 'Initializing...'} +
+ } + />
- } - /> -
- } + {debug && debugDownload && + + Download + } + {uploadError && +
+ {uploadError} +
} + } /> ); } diff --git a/src/admin/upload/index.ts b/src/admin/upload/index.ts index b908a791..d8003a57 100644 --- a/src/admin/upload/index.ts +++ b/src/admin/upload/index.ts @@ -6,6 +6,7 @@ export interface UploadState { filesLength: number fileUploadIndex: number fileUploadName: string + hideUploadPanel: boolean } export const INITIAL_UPLOAD_STATE: UploadState = { @@ -14,4 +15,5 @@ export const INITIAL_UPLOAD_STATE: UploadState = { fileUploadName: '', filesLength: 0, fileUploadIndex: 0, + hideUploadPanel: false, }; diff --git a/src/components/ImageInput.tsx b/src/components/ImageInput.tsx index 6ae4304c..c93a4097 100644 --- a/src/components/ImageInput.tsx +++ b/src/components/ImageInput.tsx @@ -1,7 +1,7 @@ 'use client'; import { blobToImage } from '@/utility/blob'; -import { useRef } from 'react'; +import { useRef, RefObject } from 'react'; import { CopyExif } from '@/lib/CopyExif'; import exifr from 'exifr'; import { clsx } from 'clsx/lite'; @@ -14,14 +14,17 @@ import { useAppState } from '@/state/AppState'; const INPUT_ID = 'file'; export default function ImageInput({ + ref, onStart, onBlobReady, shouldResize, maxSize = MAX_IMAGE_SIZE, quality = 0.8, + showUploadButton = true, showUploadStatus = true, debug, }: { + ref?: RefObject onStart?: () => void onBlobReady?: (args: { blob: Blob, @@ -32,6 +35,7 @@ export default function ImageInput({ shouldResize?: boolean maxSize?: number quality?: number + showUploadButton?: boolean showUploadStatus?: boolean debug?: boolean }) { @@ -50,7 +54,7 @@ export default function ImageInput({ } = useAppState(); return ( -
+