diff --git a/web/packages/new/photos/components/ItemCards.tsx b/web/packages/new/photos/components/ItemCards.tsx index 9a5de57b20..8c73fb3ad6 100644 --- a/web/packages/new/photos/components/ItemCards.tsx +++ b/web/packages/new/photos/components/ItemCards.tsx @@ -64,34 +64,19 @@ export const ItemCard: React.FC> = ({ if (!coverFile) return; let didCancel = false; - let thisObjectURL: string | undefined; - const go = async () => { - if (coverFaceID) { - const blob = await faceCrop(coverFaceID, coverFile); - if (!didCancel) { - thisObjectURL = blob - ? URL.createObjectURL(blob) - : undefined; - setCoverImageURL(thisObjectURL); - } - } else { - const url = await downloadManager.getThumbnailForPreview( - coverFile, - isScrolling, - ); - if (!didCancel) { - thisObjectURL = url; - setCoverImageURL(thisObjectURL); - } - } - }; - - void go(); + if (coverFaceID) { + void faceCrop(coverFaceID, coverFile).then( + (url) => !didCancel && setCoverImageURL(url), + ); + } else { + void downloadManager + .getThumbnailForPreview(coverFile, isScrolling) + .then((url) => !didCancel && setCoverImageURL(url)); + } return () => { didCancel = true; - if (thisObjectURL) URL.revokeObjectURL(thisObjectURL); }; }, [coverFile, coverFaceID, isScrolling]); diff --git a/web/packages/new/photos/components/PeopleList.tsx b/web/packages/new/photos/components/PeopleList.tsx index ba7711af59..ac41dc7deb 100644 --- a/web/packages/new/photos/components/PeopleList.tsx +++ b/web/packages/new/photos/components/PeopleList.tsx @@ -216,25 +216,22 @@ const FaceCropImageView: React.FC = ({ enteFile, placeholderDimension, }) => { - const [objectURL, setObjectURL] = useState(); + const [url, setURL] = useState(); useEffect(() => { let didCancel = false; - let thisObjectURL: string | undefined; - void faceCrop(faceID, enteFile).then((blob) => { - if (blob && !didCancel) - setObjectURL((thisObjectURL = URL.createObjectURL(blob))); - }); + void faceCrop(faceID, enteFile).then( + (url) => !didCancel && setURL(url), + ); return () => { didCancel = true; - if (thisObjectURL) URL.revokeObjectURL(thisObjectURL); }; }, [faceID, enteFile]); - return objectURL ? ( - + return url ? ( + ) : ( >(); + + /** + * Cached object URLs to face crops that we have previously vended out. + * + * The cache is only cleared on logout. + */ + faceCropObjectURLCache = new Map(); } /** State shared by the functions in this module. See {@link MLState}. */ @@ -187,6 +194,9 @@ export const logoutML = async () => { // execution contexts], it gets called first in the logout sequence, and // then this function (`logoutML`) gets called at a later point in time. + [..._state.faceCropObjectURLCache.values()].forEach((url) => + URL.revokeObjectURL(url), + ); _state = new MLState(); await clearMLDB(); }; @@ -646,7 +656,10 @@ export const getAnnotatedFacesForFile = async ( }; /** - * Return the cached face crop for the given face, regenerating it if needed. + * Return a URL to the face crop for the given face, regenerating it if needed. + * + * The resultant URL is cached (both the object URL itself, and the underlying + * file crop blob used to generete it). * * @param faceID The id of the face whose face crop we want. * @@ -662,8 +675,17 @@ export const faceCrop = async (faceID: string, enteFile: EnteFile) => { await inFlight; - const cache = await blobCache("face-crops"); - return cache.get(faceID); + let url = _state.faceCropObjectURLCache.get(faceID); + if (!url) { + const cache = await blobCache("face-crops"); + const blob = await cache.get(faceID); + if (blob) { + url = URL.createObjectURL(blob); + if (url) _state.faceCropObjectURLCache.set(faceID, url); + } + } + + return url; }; /**