@@ -8,9 +8,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
8
8
Please see LICENSE files in the repository root for full details.
9
9
*/
10
10
11
- import React , { createRef , CSSProperties } from "react" ;
11
+ import React , { createRef , CSSProperties , useRef , useState } from "react" ;
12
12
import FocusLock from "react-focus-lock" ;
13
- import { MatrixEvent } from "matrix-js-sdk/src/matrix" ;
13
+ import { MatrixEvent , parseErrorResponse } from "matrix-js-sdk/src/matrix" ;
14
14
15
15
import { _t } from "../../../languageHandler" ;
16
16
import MemberAvatar from "../avatars/MemberAvatar" ;
@@ -30,6 +30,9 @@ import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
30
30
import { getKeyBindingsManager } from "../../../KeyBindingsManager" ;
31
31
import { presentableTextForFile } from "../../../utils/FileUtils" ;
32
32
import AccessibleButton from "./AccessibleButton" ;
33
+ import Modal from "../../../Modal" ;
34
+ import ErrorDialog from "../dialogs/ErrorDialog" ;
35
+ import { FileDownloader } from "../../../utils/FileDownloader" ;
33
36
34
37
// Max scale to keep gaps around the image
35
38
const MAX_SCALE = 0.95 ;
@@ -309,15 +312,6 @@ export default class ImageView extends React.Component<IProps, IState> {
309
312
this . setZoomAndRotation ( cur + 90 ) ;
310
313
} ;
311
314
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
-
321
315
private onOpenContextMenu = ( ) : void => {
322
316
this . setState ( {
323
317
contextMenuDisplayed : true ,
@@ -555,11 +549,7 @@ export default class ImageView extends React.Component<IProps, IState> {
555
549
title = { _t ( "lightbox|rotate_right" ) }
556
550
onClick = { this . onRotateClockwiseClick }
557
551
/>
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 } />
563
553
{ contextMenuButton }
564
554
< AccessibleButton
565
555
className = "mx_ImageView_button mx_ImageView_button_close"
@@ -591,3 +581,61 @@ export default class ImageView extends React.Component<IProps, IState> {
591
581
) ;
592
582
}
593
583
}
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