1
+ from base64 import b64encode
2
+
1
3
import json
2
4
import os
3
5
import re
@@ -434,6 +436,37 @@ def download_image(image_data_timestamp, image_data, image_request):
434
436
"type" : "image/png" ,
435
437
}
436
438
439
+ @app .callback (
440
+ Output (self .id ("download-structure" ), "data" ),
441
+ Input (self .id ("download-button" ), "n_clicks" ),
442
+ [
443
+ State (self .get_kwarg_id ("download_fmt" ), "value" ),
444
+ State (self .id (), "data" ),
445
+ ],
446
+ )
447
+ def download_image (n_clicks , fmt , data ):
448
+
449
+ if not n_clicks :
450
+ raise PreventUpdate
451
+
452
+ structure = self .from_data (data )
453
+ fmt = fmt [0 ]
454
+
455
+ try :
456
+ contents = structure .to (fmt = fmt )
457
+ except Exception as exc :
458
+ # don't fail silently, tell user what went wrong
459
+ contents = exc
460
+
461
+ base64 = b64encode (contents .encode ("utf-8" )).decode ("ascii" )
462
+
463
+ return {
464
+ "content" : base64 ,
465
+ "base64" : True ,
466
+ "type" : "text/plain" ,
467
+ "filename" : f"{ structure .composition .reduced_formula } .{ fmt } " ,
468
+ }
469
+
437
470
@app .callback (
438
471
Output (self .id ("title_container" ), "children" ),
439
472
[Input (self .id ("legend_data" ), "data" )],
@@ -600,7 +633,6 @@ def _sub_layouts(self):
600
633
sceneSize = "100%" ,
601
634
** self .scene_kwargs ,
602
635
),
603
- dcc .Download (id = self .id ("download" )),
604
636
],
605
637
style = {
606
638
"width" : "100%" ,
@@ -617,6 +649,7 @@ def _sub_layouts(self):
617
649
kind = "primary" ,
618
650
id = self .id ("screenshot_button" ),
619
651
),
652
+ dcc .Download (id = self .id ("download" )),
620
653
],
621
654
# TODO: change to "bottom" when dropdown included
622
655
style = {"verticalAlign" : "top" , "display" : "inline-block" },
@@ -799,12 +832,50 @@ def _sub_layouts(self):
799
832
]
800
833
)
801
834
835
+ # human-readable label to file extension
836
+ struct_options = {
837
+ "CIF" : "cif" ,
838
+ "POSCAR" : "poscar" ,
839
+ "JSON" : "json" ,
840
+ "Prismatic" : "prismatic" ,
841
+ }
842
+
843
+ state = {"fmt" : "cif" }
844
+
845
+ download_options = self .get_choice_input (
846
+ kwarg_label = "download_fmt" ,
847
+ state = state ,
848
+ options = [{"label" : k , "value" : v } for k , v in struct_options .items ()],
849
+ style = {
850
+ "border-radius" : "4px 0px 0px 4px" ,
851
+ "width" : "10rem" ,
852
+ "height" : "1.5rem" ,
853
+ },
854
+ )
855
+
856
+ download_button = Button (
857
+ [Icon (kind = "download" ), html .Span (), "Download" ],
858
+ kind = "primary" ,
859
+ id = self .id ("download-button" ),
860
+ style = {"height" : "2.25rem" },
861
+ )
862
+
863
+ download_layout = html .Div (
864
+ [
865
+ html .Div ([download_options ], className = "control" ),
866
+ html .Div ([download_button ], className = "control" ),
867
+ dcc .Download (id = self .id ("download-structure" )),
868
+ ],
869
+ className = "field has-addons" ,
870
+ )
871
+
802
872
return {
803
873
"struct" : struct_layout ,
804
874
"screenshot" : screenshot_layout ,
805
875
"options" : options_layout ,
806
876
"title" : title_layout ,
807
877
"legend" : legend_layout ,
878
+ "download" : download_layout ,
808
879
}
809
880
810
881
def layout (self , size : str = "500px" ) -> html .Div :
@@ -1009,3 +1080,9 @@ def legend_layout(self):
1009
1080
:return: A layout including a legend for the structure/molecule.
1010
1081
"""
1011
1082
return self ._sub_layouts ["legend" ]
1083
+
1084
+ def download_layout (self ):
1085
+ """
1086
+ :return: A layout including a download button to download the structure/molecule.
1087
+ """
1088
+ return self ._sub_layouts ["download" ]
0 commit comments