diff --git a/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/index.tsx b/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/index.tsx index e3e3c41d7c..766e8b0550 100644 --- a/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/index.tsx +++ b/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/index.tsx @@ -11,13 +11,13 @@ import { photosDialogZIndex } from "@/new/photos/components/z-index"; import downloadManager from "@/new/photos/services/download"; import { AppContext } from "@/new/photos/types/context"; import { EnteFile } from "@/new/photos/types/file"; +import { downloadAndRevokeObjectURL } from "@/new/photos/utils/web"; import { ensure } from "@/utils/ensure"; import { CenteredFlex, HorizontalFlex, } from "@ente/shared/components/Container"; import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem"; -import { downloadUsingAnchor } from "@ente/shared/utils"; import ChevronRightIcon from "@mui/icons-material/ChevronRight"; import CloseIcon from "@mui/icons-material/Close"; import CloudUploadIcon from "@mui/icons-material/CloudUpload"; @@ -462,8 +462,7 @@ const ImageEditorOverlay = (props: IProps) => { if (!canvasRef.current) return; const f = await getEditedFile(); - // Revokes the URL after downloading. - downloadUsingAnchor(URL.createObjectURL(f), f.name); + downloadAndRevokeObjectURL(URL.createObjectURL(f), f.name); }; const saveCopyToEnte = async () => { diff --git a/web/apps/photos/src/utils/file/index.ts b/web/apps/photos/src/utils/file/index.ts index 23b4ef69ad..8fdf689d16 100644 --- a/web/apps/photos/src/utils/file/index.ts +++ b/web/apps/photos/src/utils/file/index.ts @@ -19,10 +19,10 @@ import { detectFileTypeInfo } from "@/new/photos/utils/detect-type"; import { mergeMetadata } from "@/new/photos/utils/file"; import { safeFileName } from "@/new/photos/utils/native-fs"; import { writeStream } from "@/new/photos/utils/native-stream"; +import { downloadAndRevokeObjectURL } from "@/new/photos/utils/web"; import { withTimeout } from "@/utils/promise"; import { LS_KEYS, getData } from "@ente/shared/storage/localStorage"; import type { User } from "@ente/shared/user/types"; -import { downloadUsingAnchor } from "@ente/shared/utils"; import { t } from "i18next"; import { addMultipleToFavorites, @@ -70,8 +70,8 @@ export async function downloadFile(file: EnteFile) { const tempVideoURL = URL.createObjectURL( new Blob([videoData], { type: videoType.mimeType }), ); - downloadUsingAnchor(tempImageURL, imageFileName); - downloadUsingAnchor(tempVideoURL, videoFileName); + downloadAndRevokeObjectURL(tempImageURL, imageFileName); + downloadAndRevokeObjectURL(tempVideoURL, videoFileName); } else { const fileType = await detectFileTypeInfo( new File([fileBlob], file.metadata.title), @@ -81,7 +81,7 @@ export async function downloadFile(file: EnteFile) { ).blob(); fileBlob = new Blob([fileBlob], { type: fileType.mimeType }); const tempURL = URL.createObjectURL(fileBlob); - downloadUsingAnchor(tempURL, file.metadata.title); + downloadAndRevokeObjectURL(tempURL, file.metadata.title); } } catch (e) { log.error("failed to download file", e); diff --git a/web/packages/new/photos/components/RecoveryKey.tsx b/web/packages/new/photos/components/RecoveryKey.tsx index b0c21453da..b8cf7dca14 100644 --- a/web/packages/new/photos/components/RecoveryKey.tsx +++ b/web/packages/new/photos/components/RecoveryKey.tsx @@ -12,7 +12,6 @@ import { ensure } from "@/utils/ensure"; import CodeBlock from "@ente/shared/components/CodeBlock"; import DialogTitleWithCloseButton from "@ente/shared/components/DialogBox/TitleWithCloseButton"; import { getRecoveryKey } from "@ente/shared/crypto/helpers"; -import { downloadAsFile } from "@ente/shared/utils"; import { Box, Button, @@ -25,6 +24,7 @@ import { import * as bip39 from "bip39"; import { t } from "i18next"; import { useEffect, useState } from "react"; +import { downloadString } from "../utils/web"; // mobile client library only supports english. bip39.setDefaultWordlist("english"); @@ -62,7 +62,7 @@ export const RecoveryKey: React.FC = ({ }, [open]); function onSaveClick() { - downloadAsFile(RECOVERY_KEY_FILE_NAME, ensure(recoveryKey)); + downloadRecoveryKeyMnemonic(ensure(recoveryKey)); onClose(); } @@ -111,3 +111,6 @@ const DashedBorderWrapper = styled(Box)(({ theme }) => ({ const getRecoveryKeyMnemonic = async () => bip39.entropyToMnemonic(await getRecoveryKey()); + +const downloadRecoveryKeyMnemonic = (key: string) => + downloadString(key, RECOVERY_KEY_FILE_NAME); diff --git a/web/packages/new/photos/utils/web.ts b/web/packages/new/photos/utils/web.ts index 7236cab798..92ba90a4ac 100644 --- a/web/packages/new/photos/utils/web.ts +++ b/web/packages/new/photos/utils/web.ts @@ -21,3 +21,45 @@ export const initiateEmail = (email: string) => { a.rel = "noopener"; a.click(); }; + +/** + * Download the asset at the given {@link url} to a file on the user's download + * folder by appending a temporary element to the DOM. + * + * @param url The URL that we want to download. See also + * {@link downloadAndRevokeObjectURL} and {@link downloadString}. + * + * @param fileName The name of downloaded file. + */ +export const downloadURL = (url: string, fileName: string) => { + const a = document.createElement("a"); + a.style.display = "none"; + a.href = url; + a.download = fileName; + document.body.appendChild(a); + a.click(); + URL.revokeObjectURL(url); + a.remove(); +}; + +/** + * A variant of {@link downloadURL} that also revokes the provided + * {@link objectURL} after initiating the download. + */ +export const downloadAndRevokeObjectURL = (url: string, fileName: string) => { + downloadURL(url, fileName); + URL.revokeObjectURL(url); +}; + +/** + * Save the given string {@link s} as a file in the user's download folder. + * + * @param s The string to save. + * + * @param fileName The name of the file that gets saved. + */ +export const downloadString = (s: string, fileName: string) => { + const file = new Blob([s], { type: "text/plain" }); + const fileURL = URL.createObjectURL(file); + downloadAndRevokeObjectURL(fileURL, fileName); +}; diff --git a/web/packages/shared/utils/index.ts b/web/packages/shared/utils/index.ts index d8aae6cb55..fd372fd7cf 100644 --- a/web/packages/shared/utils/index.ts +++ b/web/packages/shared/utils/index.ts @@ -1,25 +1,6 @@ import { ensure } from "@/utils/ensure"; import { wait } from "@/utils/promise"; -export function downloadAsFile(filename: string, content: string) { - const file = new Blob([content], { - type: "text/plain", - }); - const fileURL = URL.createObjectURL(file); - downloadUsingAnchor(fileURL, filename); -} - -export function downloadUsingAnchor(link: string, name: string) { - const a = document.createElement("a"); - a.style.display = "none"; - a.href = link; - a.download = name; - document.body.appendChild(a); - a.click(); - URL.revokeObjectURL(link); - a.remove(); -} - export async function retryAsyncFunction( request: (abort?: () => void) => Promise, waitTimeBeforeNextTry?: number[],