@@ -2,7 +2,7 @@ import createFetchClient from "openapi-fetch";
2
2
import createClient from "openapi-react-query" ;
3
3
import type { paths } from "../api.gen" ;
4
4
import { C } from "../configuration" ;
5
- import { useState } from "react " ;
5
+ import { useDownload } from "../hooks/useDownload " ;
6
6
import { PathsWithMethod } from "openapi-typescript-helpers" ;
7
7
8
8
export const fetchClient = createFetchClient < paths > ( {
@@ -11,100 +11,16 @@ export const fetchClient = createFetchClient<paths>({
11
11
} ) ;
12
12
const apiClient = createClient ( fetchClient ) ;
13
13
14
- export const useDownload = ( ) => {
15
- const [ error , setError ] = useState < Error | unknown | null > ( null ) ;
16
- const [ isDownloading , setIsDownloading ] = useState < boolean > ( false ) ;
17
- const [ progress , setProgress ] = useState < number | null > ( null ) ;
18
-
19
- const handleResponse = async ( response : Response ) : Promise < string > => {
20
- if ( ! response . ok ) {
21
- throw new Error ( "Could not download file" ) ;
22
- }
23
-
24
- const contentLength = response . headers . get ( "content-length" ) ;
25
- const reader = response . body ?. getReader ( ) ;
26
-
27
- if ( ! contentLength || ! reader ) {
28
- const blob = await response . blob ( ) ;
29
-
30
- return createBlobURL ( blob ) ;
31
- }
32
-
33
- const stream = await getStream ( contentLength , reader ) ;
34
- const newResponse = new Response ( stream ) ;
35
- const blob = await newResponse . blob ( ) ;
36
-
37
- return createBlobURL ( blob ) ;
38
- } ;
39
-
40
- const getStream = async ( contentLength : string , reader : ReadableStreamDefaultReader < Uint8Array > ) : Promise < ReadableStream < Uint8Array > > => {
41
- let loaded = 0 ;
42
- const total = parseInt ( contentLength , 10 ) ;
43
-
44
- return new ReadableStream < Uint8Array > ( {
45
- async start ( controller ) {
46
- try {
47
- for ( ; ; ) {
48
- const { done, value } = await reader . read ( ) ;
49
-
50
- if ( done ) break ;
51
-
52
- loaded += value . byteLength ;
53
- const percentage = Math . trunc ( ( loaded / total ) * 100 ) ;
54
- setProgress ( percentage ) ;
55
- controller . enqueue ( value ) ;
56
- }
57
- } catch ( error ) {
58
- controller . error ( error ) ;
59
- throw error ;
60
- } finally {
61
- controller . close ( ) ;
62
- }
63
- } ,
64
- } ) ;
65
- } ;
66
-
67
- const createBlobURL = ( blob : Blob ) : string => {
68
- return window . URL . createObjectURL ( blob ) ;
69
- } ;
70
-
71
- const handleDownload = ( fileName : string , url : string ) => {
72
- const link = document . createElement ( "a" ) ;
73
-
74
- link . href = url ;
75
- link . setAttribute ( "download" , fileName ) ;
76
- document . body . appendChild ( link ) ;
77
- link . click ( ) ;
78
- document . body . removeChild ( link ) ;
79
- window . URL . revokeObjectURL ( url ) ;
80
- } ;
81
-
82
- const downloadFile = async ( fileName : string , fileUrl : PathsWithMethod < paths , "get" > ) => {
83
- setIsDownloading ( true ) ;
84
- setError ( null ) ;
85
- setProgress ( null ) ;
86
-
87
- try {
88
- const req = await fetchClient . GET ( fileUrl , { parseAs : "stream" } ) ;
89
- const url = await handleResponse ( req . response ) ;
90
-
91
- handleDownload ( fileName , url ) ;
92
- } catch ( error ) {
93
- setError ( error ) ;
94
- } finally {
95
- setIsDownloading ( false ) ;
96
- }
97
- } ;
98
-
99
- return {
100
- error,
101
- isDownloading,
102
- progress,
103
- downloadFile,
104
- } ;
14
+ // Wrap the useDownload hook to download a file from the API
15
+ const useApiDownload = ( fileUrl : PathsWithMethod < paths , "get" > , fileName : string ) => {
16
+ return useDownload ( fileUrl , fileName , async ( ) => {
17
+ // Fetch the file as a stream so the fetch client doesn't try to parse it as JSON and we can track the download progress
18
+ const get = await fetchClient . GET ( fileUrl , { parseAs : "stream" } ) ;
19
+ return get . response ;
20
+ } ) ;
105
21
} ;
106
22
107
23
export const $api = {
108
24
...apiClient ,
109
- useDownload,
25
+ useDownload : useApiDownload ,
110
26
} ;
0 commit comments