13
13
right : 16px ;
14
14
width : 100% ;
15
15
text-align : right;
16
- }
16
+ }
17
+
18
+ # loading-spinner {
19
+ position : fixed;
20
+ top : 0 ;
21
+ left : 0 ;
22
+ width : 100% ;
23
+ height : 100% ;
24
+ background-color : rgba (0 , 0 , 0 , 0.5 ); /* semi-transparent background */
25
+ z-index : 9999 ; /* high z-index to ensure it's on top */
26
+ display : flex;
27
+ justify-content : center;
28
+ align-items : center;
29
+ }
30
+
31
+ .spinner {
32
+ border : 4px solid # f3f3f3 ;
33
+ border-top : 4px solid # 3498db ;
34
+ border-radius : 50% ;
35
+ width : 40px ;
36
+ height : 40px ;
37
+ animation : spin 1s linear infinite;
38
+ }
39
+
40
+ @keyframes spin {
41
+ 0% { transform : rotate (0deg ); }
42
+ 100% { transform : rotate (360deg ); }
43
+ }
17
44
</ style >
18
45
19
46
< script type ="text/javascript ">
20
47
let start_ts_inst = null ;
21
48
let end_ts_inst = null ;
22
49
let dialog = null ;
23
50
24
- $ ( document ) . ready ( function ( ) {
25
- dialog = document . getElementById ( 'export-data' ) ;
26
- // A submit handler would avoid this getting called when the dialog is dismissed using
27
- // the escape key, but it does not get the correct value for dialog.returnValue.
28
- //
29
- // The close handler gets called all the time but at least dialog.returnValue has a
30
- // valid value.
31
- dialog . addEventListener ( "close" , function ( event ) {
32
- console . log ( $ ( "#start_ts" ) . val ( ) ) ;
33
- console . log ( $ ( "#end_ts" ) . val ( ) ) ;
34
- console . log ( dialog . returnValue ) ;
51
+ async function doFetch ( event ) {
52
+ console . log ( $ ( "#start_ts" ) . val ( ) ) ;
53
+ console . log ( $ ( "#end_ts" ) . val ( ) ) ;
54
+ console . log ( dialog . returnValue ) ;
35
55
36
- if ( start_ts_inst ) {
37
- start_ts_inst . destroy ( ) ;
38
- }
39
- if ( end_ts_inst ) {
40
- end_ts_inst . destroy ( ) ;
41
- }
56
+ if ( start_ts_inst ) {
57
+ start_ts_inst . destroy ( ) ;
58
+ }
59
+ if ( end_ts_inst ) {
60
+ end_ts_inst . destroy ( ) ;
61
+ }
42
62
43
- if ( 'Export' . localeCompare ( dialog . returnValue ) === 0 ) {
63
+ if ( 'Export' . localeCompare ( dialog . returnValue ) === 0 ) {
64
+ try {
65
+ showSpinner ( ) ;
44
66
console . log ( 'Exporting data' ) ;
45
67
const formElement = document . getElementById ( 'single-ld-export' ) ;
46
68
const formData = new FormData ( formElement ) ;
47
69
console . log ( formData ) ;
48
- fetch ( formElement . action , {
70
+ x = fetch ( formElement . action , {
49
71
method : 'POST' ,
50
72
body : formData
51
73
} )
55
77
}
56
78
const blob = await response . blob ( ) ;
57
79
const filename = response . headers . get ( 'Content-Disposition' ) ?. split ( 'filename=' ) [ 1 ] ?. replace ( / [ ' " ] / g, '' ) || 'download.csv' ;
58
-
80
+
59
81
// Check if the showSaveFilePicker API is available
60
82
if ( 'showSaveFilePicker' in window ) {
61
- try {
83
+ try {
62
84
const handle = await window . showSaveFilePicker ( {
63
- suggestedName : filename ,
64
- types : [ {
85
+ suggestedName : filename ,
86
+ types : [ {
65
87
description : 'CSV File' ,
66
88
accept : { 'text/csv' : [ '.csv' ] } ,
67
- } ] ,
89
+ } ] ,
68
90
} ) ;
69
91
const writable = await handle . createWritable ( ) ;
70
92
await writable . write ( blob ) ;
71
93
await writable . close ( ) ;
72
- } catch ( err ) {
94
+ } catch ( err ) {
73
95
if ( err . name !== 'AbortError' ) {
74
- console . error ( 'Failed to save file:' , err ) ;
75
- // Fallback to the older method
76
- saveBlobAsFile ( blob , filename ) ;
96
+ console . error ( 'Failed to save file:' , err ) ;
97
+ // Fallback to the older method
98
+ saveBlobAsFile ( blob , filename ) ;
77
99
}
78
- }
100
+ }
79
101
} else {
80
- // Fallback for browsers that don't support showSaveFilePicker
81
- saveBlobAsFile ( blob , filename ) ;
102
+ // Fallback for browsers that don't support showSaveFilePicker
103
+ saveBlobAsFile ( blob , filename ) ;
82
104
}
83
105
} )
84
106
. catch ( error => {
85
107
console . error ( 'Error:' , error ) ;
108
+ // Does this show up when the spinnder is active?
86
109
alert ( "Error occured on submission" , error ) ;
87
110
} ) ;
111
+
112
+ // Wait for the fetch to finish so the spinner shows up.
113
+ await x ;
114
+ } finally {
115
+ hideSpinner ( ) ;
88
116
}
89
- } ) ;
117
+ }
118
+ }
119
+
120
+ $ ( document ) . ready ( function ( ) {
121
+ dialog = document . getElementById ( 'export-data' ) ;
122
+ // A submit handler would avoid this getting called when the dialog is dismissed using
123
+ // the escape key, but it does not get the correct value for dialog.returnValue.
124
+ //
125
+ // The close handler gets called all the time but at least dialog.returnValue has a
126
+ // valid value.
127
+ dialog . addEventListener ( "close" , doFetch ) ;
90
128
} ) ;
91
129
92
130
function exportData ( l_uid , name ) {
121
159
a . click ( ) ;
122
160
window . URL . revokeObjectURL ( url ) ;
123
161
document . body . removeChild ( a ) ;
124
- }
162
+ }
163
+
164
+ function showSpinner ( ) {
165
+ console . log ( 'showing spinner' ) ;
166
+ document . getElementById ( 'loading-spinner' ) . style . display = 'flex' ;
167
+ }
168
+
169
+ function hideSpinner ( ) {
170
+ console . log ( 'hiding spinner' ) ;
171
+ document . getElementById ( 'loading-spinner' ) . style . display = 'none' ;
172
+ }
125
173
</ script >
126
174
175
+ <!-- Are the nested divs necessary or is the inner one sufficient? -->
176
+ < div id ="loading-spinner " style ="display: none; ">
177
+ < div class ="spinner "> </ div >
178
+ </ div >
179
+
127
180
< dialog id ="export-data ">
128
181
< header >
129
182
< h3 style ="padding: 8px "> Export data</ h3 >
@@ -157,8 +210,8 @@ <h3 style="padding: 8px">Export data</h3>
157
210
< li > < span class ="btn " onclick ="handleMapping('logical device') "> Update Mapping</ span > </ li >
158
211
< li > < span class ="btn " onclick ="handleEndMapping('{{ ld_data.uid }}', 'LD') "> End Mapping</ span > </ li >
159
212
{% if deviceMappings | length > 0 %}
160
- < li > < span class ="btn " onclick ="handleToggleMapping('{{ deviceMappings[0].pd.uid }}','{{ deviceMappings[0].is_active }}', 'PD') "> {{ "Pause Mapping" if deviceMappings[0].is_active == True else "Resume Mapping" }}</ span > </ li >
161
- {% endif %}
213
+ < li > < span class ="btn " onclick ="handleToggleMapping('{{ deviceMappings[0].pd.uid }}','{{ deviceMappings[0].is_active }}', 'PD') "> {{ "Pause Mapping" if deviceMappings[0].is_active == True else "Resume Mapping" }}</ span > </ li >
214
+ {% endif %}
162
215
</ ul >
163
216
</ div >
164
217
</ div >
@@ -262,4 +315,4 @@ <h3 id="mapping-heading">Mapping</h3>
262
315
</ div >
263
316
</ section >
264
317
265
- {% endblock %}
318
+ {% endblock %}
0 commit comments