Skip to content

Commit a747af5

Browse files
committed
Add basic download button
1 parent 489861b commit a747af5

File tree

2 files changed

+80
-1
lines changed

2 files changed

+80
-1
lines changed

crystal_toolkit/apps/examples/structure.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
structure_component.title_layout(),
4040
html.H3("Legend Layout"),
4141
structure_component.legend_layout(),
42+
html.H3("Download Layout"),
43+
structure_component.download_layout(),
4244
]
4345
)
4446

crystal_toolkit/components/structure.py

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from base64 import b64encode
2+
13
import json
24
import os
35
import re
@@ -434,6 +436,37 @@ def download_image(image_data_timestamp, image_data, image_request):
434436
"type": "image/png",
435437
}
436438

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+
437470
@app.callback(
438471
Output(self.id("title_container"), "children"),
439472
[Input(self.id("legend_data"), "data")],
@@ -600,7 +633,6 @@ def _sub_layouts(self):
600633
sceneSize="100%",
601634
**self.scene_kwargs,
602635
),
603-
dcc.Download(id=self.id("download")),
604636
],
605637
style={
606638
"width": "100%",
@@ -617,6 +649,7 @@ def _sub_layouts(self):
617649
kind="primary",
618650
id=self.id("screenshot_button"),
619651
),
652+
dcc.Download(id=self.id("download")),
620653
],
621654
# TODO: change to "bottom" when dropdown included
622655
style={"verticalAlign": "top", "display": "inline-block"},
@@ -799,12 +832,50 @@ def _sub_layouts(self):
799832
]
800833
)
801834

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+
802872
return {
803873
"struct": struct_layout,
804874
"screenshot": screenshot_layout,
805875
"options": options_layout,
806876
"title": title_layout,
807877
"legend": legend_layout,
878+
"download": download_layout,
808879
}
809880

810881
def layout(self, size: str = "500px") -> html.Div:
@@ -1009,3 +1080,9 @@ def legend_layout(self):
10091080
:return: A layout including a legend for the structure/molecule.
10101081
"""
10111082
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

Comments
 (0)