@@ -8,9 +8,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
88Please see LICENSE files in the repository root for full details.
99*/
1010
11- import React , { createRef , CSSProperties } from "react" ;
11+ import React , { createRef , CSSProperties , useRef , useState } from "react" ;
1212import FocusLock from "react-focus-lock" ;
13- import { MatrixEvent } from "matrix-js-sdk/src/matrix" ;
13+ import { MatrixEvent , parseErrorResponse } from "matrix-js-sdk/src/matrix" ;
1414
1515import { _t } from "../../../languageHandler" ;
1616import MemberAvatar from "../avatars/MemberAvatar" ;
@@ -30,6 +30,9 @@ import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
3030import { getKeyBindingsManager } from "../../../KeyBindingsManager" ;
3131import { presentableTextForFile } from "../../../utils/FileUtils" ;
3232import AccessibleButton from "./AccessibleButton" ;
33+ import Modal from "../../../Modal" ;
34+ import ErrorDialog from "../dialogs/ErrorDialog" ;
35+ import { FileDownloader } from "../../../utils/FileDownloader" ;
3336
3437// Max scale to keep gaps around the image
3538const MAX_SCALE = 0.95 ;
@@ -309,15 +312,6 @@ export default class ImageView extends React.Component<IProps, IState> {
309312 this . setZoomAndRotation ( cur + 90 ) ;
310313 } ;
311314
312- private onDownloadClick = ( ) : void => {
313- const a = document . createElement ( "a" ) ;
314- a . href = this . props . src ;
315- if ( this . props . name ) a . download = this . props . name ;
316- a . target = "_blank" ;
317- a . rel = "noreferrer noopener" ;
318- a . click ( ) ;
319- } ;
320-
321315 private onOpenContextMenu = ( ) : void => {
322316 this . setState ( {
323317 contextMenuDisplayed : true ,
@@ -555,11 +549,7 @@ export default class ImageView extends React.Component<IProps, IState> {
555549 title = { _t ( "lightbox|rotate_right" ) }
556550 onClick = { this . onRotateClockwiseClick }
557551 />
558- < AccessibleButton
559- className = "mx_ImageView_button mx_ImageView_button_download"
560- title = { _t ( "action|download" ) }
561- onClick = { this . onDownloadClick }
562- />
552+ < DownloadButton url = { this . props . src } fileName = { this . props . name } />
563553 { contextMenuButton }
564554 < AccessibleButton
565555 className = "mx_ImageView_button mx_ImageView_button_close"
@@ -591,3 +581,61 @@ export default class ImageView extends React.Component<IProps, IState> {
591581 ) ;
592582 }
593583}
584+
585+ function DownloadButton ( { url, fileName } : { url : string ; fileName ?: string } ) : JSX . Element {
586+ const downloader = useRef ( new FileDownloader ( ) ) . current ;
587+ const [ loading , setLoading ] = useState ( false ) ;
588+ const blobRef = useRef < Blob > ( ) ;
589+
590+ function showError ( e : unknown ) : void {
591+ Modal . createDialog ( ErrorDialog , {
592+ title : _t ( "timeline|download_failed" ) ,
593+ description : (
594+ < >
595+ < div > { _t ( "timeline|download_failed_description" ) } </ div >
596+ < div > { e instanceof Error ? e . toString ( ) : "" } </ div >
597+ </ >
598+ ) ,
599+ } ) ;
600+ setLoading ( false ) ;
601+ }
602+
603+ const onDownloadClick = async ( ) : Promise < void > => {
604+ try {
605+ if ( loading ) return ;
606+ setLoading ( true ) ;
607+
608+ if ( blobRef . current ) {
609+ // Cheat and trigger a download, again.
610+ return downloadBlob ( blobRef . current ) ;
611+ }
612+
613+ const res = await fetch ( url ) ;
614+ if ( ! res . ok ) {
615+ throw parseErrorResponse ( res , await res . text ( ) ) ;
616+ }
617+ const blob = await res . blob ( ) ;
618+ blobRef . current = blob ;
619+ await downloadBlob ( blob ) ;
620+ } catch ( e ) {
621+ showError ( e ) ;
622+ }
623+ } ;
624+
625+ async function downloadBlob ( blob : Blob ) : Promise < void > {
626+ await downloader . download ( {
627+ blob,
628+ name : fileName ?? _t ( "common|image" ) ,
629+ } ) ;
630+ setLoading ( false ) ;
631+ }
632+
633+ return (
634+ < AccessibleButton
635+ className = "mx_ImageView_button mx_ImageView_button_download"
636+ title = { loading ? _t ( "timeline|download_action_downloading" ) : _t ( "action|download" ) }
637+ onClick = { onDownloadClick }
638+ disabled = { loading }
639+ />
640+ ) ;
641+ }
0 commit comments