Enhancement: Save image on CTRL+S (#30330)
* Save image on CTRL+S * fixed cosmetic comments * fixed test * refactored out downloading functionality from buttons to useDownloadMedia hook * ImageView CTRL+S use button component * added CTRL+S test & lint * removed forwardRef * fix lint * i18n
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { parseErrorResponse } from "matrix-js-sdk/src/matrix";
|
||||
import { useRef, useState, useMemo, useEffect } from "react";
|
||||
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import ErrorDialog from "../components/views/dialogs/ErrorDialog";
|
||||
import { _t } from "../languageHandler";
|
||||
import Modal from "../Modal";
|
||||
import { FileDownloader } from "../utils/FileDownloader";
|
||||
import { MediaEventHelper } from "../utils/MediaEventHelper";
|
||||
import ModuleApi from "../modules/Api";
|
||||
|
||||
export interface UseDownloadMediaReturn {
|
||||
download: () => Promise<void>;
|
||||
loading: boolean;
|
||||
canDownload: boolean;
|
||||
}
|
||||
|
||||
export function useDownloadMedia(url: string, fileName?: string, mxEvent?: MatrixEvent): UseDownloadMediaReturn {
|
||||
const downloader = useRef(new FileDownloader()).current;
|
||||
const blobRef = useRef<Blob>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [canDownload, setCanDownload] = useState<boolean>(true);
|
||||
|
||||
const mediaEventHelper = useMemo(() => (mxEvent ? new MediaEventHelper(mxEvent) : undefined), [mxEvent]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!mxEvent) return;
|
||||
|
||||
const hints = ModuleApi.customComponents.getHintsForMessage(mxEvent);
|
||||
if (hints?.allowDownloadingMedia) {
|
||||
setCanDownload(false);
|
||||
hints
|
||||
.allowDownloadingMedia()
|
||||
.then(setCanDownload)
|
||||
.catch((err: any) => {
|
||||
logger.error(`Failed to check media download permission for ${mxEvent.event.event_id}`, err);
|
||||
|
||||
setCanDownload(false);
|
||||
});
|
||||
} else {
|
||||
setCanDownload(true);
|
||||
}
|
||||
}, [mxEvent]);
|
||||
|
||||
const download = async (): Promise<void> => {
|
||||
if (loading) return;
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
if (blobRef.current) {
|
||||
return downloadBlob(blobRef.current);
|
||||
}
|
||||
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) {
|
||||
throw parseErrorResponse(res, await res.text());
|
||||
}
|
||||
|
||||
const blob = await res.blob();
|
||||
blobRef.current = blob;
|
||||
|
||||
await downloadBlob(blob);
|
||||
} catch (e) {
|
||||
showError(e);
|
||||
}
|
||||
};
|
||||
|
||||
const downloadBlob = async (blob: Blob): Promise<void> => {
|
||||
await downloader.download({
|
||||
blob,
|
||||
name: mediaEventHelper?.fileName ?? fileName ?? _t("common|image"),
|
||||
});
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const showError = (e: unknown): void => {
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("timeline|download_failed"),
|
||||
description: `${_t("timeline|download_failed_description")}\n\n${String(e)}`,
|
||||
});
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return { download, loading, canDownload };
|
||||
}
|
||||
Reference in New Issue
Block a user