1
+ from importlib import import_module
2
+ from inspect import getsource
3
+ from copy import deepcopy
4
+ import json
5
+ import os
6
+
7
+ import dash
8
+ import dash_core_components as dcc
9
+ import dash_html_components as html
10
+ from dash .dependencies import Input , Output , State
11
+ import dash_bootstrap_components as dbc
12
+
13
+
14
+ def prepend_recursive (component , prefix : str ) -> None :
15
+ """in-place modifications"""
16
+ if hasattr (component , "id" ):
17
+ if type (component .id ) == str :
18
+ component .id = prefix + component .id
19
+ elif type (component .id ) == dict :
20
+ key = "type"
21
+ if key in component .id :
22
+ component .id [key ] = prefix + component .id [key ]
23
+
24
+ if hasattr (component , "children" ) and component .children is not None :
25
+ for child in component .children :
26
+ prepend_recursive (child , prefix )
27
+
28
+
29
+ def prepend_list_of_dict (ls : list , prefix : str ) -> list :
30
+ new_ls = []
31
+
32
+ for di in ls :
33
+ di = deepcopy (di )
34
+ try : # is a dictionary
35
+ di_id = json .loads (di ["id" ])
36
+ key = "type"
37
+ if key in di_id :
38
+ di_id [key ] = prefix + di_id [key ]
39
+
40
+ di ["id" ] = json .dumps (di_id ).replace (" " , "" )
41
+
42
+ except ValueError : # is a string
43
+ di ["id" ] = prefix + di ["id" ]
44
+
45
+ new_ls .append (di )
46
+ return new_ls
47
+
48
+
49
+ def prepend_callback_map (di : dict , prefix : str ) -> dict :
50
+ new_di = {}
51
+ for k , v in di .items ():
52
+ v = deepcopy (v )
53
+ v ["inputs" ] = prepend_list_of_dict (v ["inputs" ], prefix )
54
+ v ["state" ] = prepend_list_of_dict (v ["state" ], prefix )
55
+ new_di [prefix + k ] = v
56
+
57
+ return new_di
58
+
59
+
60
+ def prepend_callback_list (ls : list , prefix : str ) -> list :
61
+ new_ls = []
62
+ for di in ls :
63
+ di = deepcopy (di )
64
+ if type (di ['output' ]) == list :
65
+ di ["output" ] = prepend_list_of_dict (di ["output" ], prefix )
66
+ else :
67
+ di ["output" ] = prefix + di ["output" ]
68
+ di ["inputs" ] = prepend_list_of_dict (di ["inputs" ], prefix )
69
+ di ["state" ] = prepend_list_of_dict (di ["state" ], prefix )
70
+
71
+ new_ls .append (di )
72
+
73
+ return new_ls
74
+
75
+
76
+ def Header (name , app ):
77
+ title = html .H2 (name , style = {"display" : "inline-block" })
78
+ logo = html .Img (
79
+ src = app .get_asset_url ("dash-logo.png" ),
80
+ style = {"height" : 60 },
81
+ )
82
+ link = html .A (logo , href = "https://plotly.com/dash/" , target = "_blank" )
83
+
84
+ return html .Div ([link , title ])
85
+
86
+
87
+ def display_demo (name , layout , code ):
88
+ download_btn = html .A (
89
+ html .Button (
90
+ "Download" ,
91
+ style = {
92
+ "width" : "90px" ,
93
+ "margin" : "auto" ,
94
+ "padding" : "0px" ,
95
+ "font-size" : "10px" ,
96
+ "height" : "35px" ,
97
+ "border-radius" : "2px" ,
98
+ },
99
+ ),
100
+ href = app .get_asset_url (name + ".py" ),
101
+ download = "app.py" ,
102
+ style = {"position" : "absolute" , "top" : "1.5em" , "right" : "1.5em" },
103
+ )
104
+ return html .Div (
105
+ [
106
+ html .Div (
107
+ [
108
+ download_btn ,
109
+ dcc .Markdown (f"```\n { code } \n ```" ),
110
+ ],
111
+ style = {
112
+ "float" : "left" ,
113
+ "width" : "49%" ,
114
+ "height" : "85vh" ,
115
+ "overflow-y" : "auto" ,
116
+ "position" : "relative" ,
117
+ "background-color" : "#F7FAFC" ,
118
+ "border" : "1px solid #A1ACC3" ,
119
+ "border-right" : "none" ,
120
+ },
121
+ ),
122
+ html .Div (
123
+ layout ,
124
+ style = {
125
+ "float" : "left" ,
126
+ "width" : "48%" ,
127
+ "padding" : "5px 1% 5px 1%" ,
128
+ "height" : "calc(85vh - 10px)" ,
129
+ "overflow-y" : "auto" ,
130
+ "border" : "1px solid #A1ACC3" ,
131
+ },
132
+ ),
133
+ ]
134
+ )
135
+
136
+
137
+ prefix_ignored = [
138
+
139
+ ]
140
+
141
+ ignored_pages = [
142
+ 'assets' ,
143
+ 'data'
144
+ ]
145
+
146
+
147
+ app = dash .Dash (__name__ , suppress_callback_exceptions = True , external_stylesheets = [dbc .themes .BOOTSTRAP ])
148
+ server = app .server
149
+
150
+ app_subdomain = os .getenv ("APP_SUBDOMAIN" , "dash-vtk-explorer" )
151
+
152
+ pages = [p for p in sorted (os .listdir ()) if os .path .isdir (p ) and p not in ignored_pages ]
153
+ modules = {p : import_module (f"{ p } .app" ) for p in pages }
154
+ apps = {p : m .app for p , m in modules .items ()}
155
+ source_codes = {p : getsource (m ) for p , m in modules .items ()}
156
+ notfound_404 = html .Div (
157
+ [
158
+ html .H1 ("404" ),
159
+ "Webpage not found. Please contact us if a page is supposed to be here." ,
160
+ ]
161
+ )
162
+
163
+ app .layout = dbc .Container (
164
+ [
165
+ # dbc.Row([
166
+ # dbc.Col(
167
+ # Header("Dash VTK Explorer", app),
168
+ # width=8
169
+ # ),
170
+ # dbc.Col(
171
+ # dcc.Dropdown(
172
+ # id="app-choice",
173
+ # placeholder="Please select an app...",
174
+ # style={"width": "100%"},
175
+ # options=[{"label": x, "value": x} for x in pages],
176
+ # ),
177
+ # width=4
178
+ # )
179
+ # ]),
180
+ # html.Hr(),
181
+ dcc .Location (id = "url" , refresh = False ),
182
+ html .Div (id = "display" ),
183
+ ],
184
+ fluid = True
185
+ )
186
+
187
+ for k in apps :
188
+ new_callback_map = apps [k ].callback_map
189
+ new_callback_list = apps [k ]._callback_list
190
+
191
+ # Prepend to layout IDs recursively in-place
192
+ # if k in prefix_ignored:
193
+ # new_callback_map = apps[k].callback_map
194
+ # new_callback_list = apps[k]._callback_list
195
+ # else:
196
+ # prepend_recursive(apps[k].layout, prefix=k + "-")
197
+ # new_callback_map = prepend_callback_map(apps[k].callback_map, prefix=k + "-")
198
+ # new_callback_list = prepend_callback_list(apps[k]._callback_list, prefix=k + "-")
199
+
200
+ app .callback_map .update (new_callback_map )
201
+ app ._callback_list .extend (new_callback_list )
202
+
203
+
204
+ @app .callback (Output ("url" , "pathname" ), Input ("app-choice" , "value" ))
205
+ def update_url (name ):
206
+ if name is None :
207
+ return dash .no_update
208
+ return f"/{ app_subdomain } /{ name } "
209
+
210
+
211
+ @app .callback (Output ("display" , "children" ), [Input ("url" , "pathname" )])
212
+ def display_content (pathname ):
213
+ if app_subdomain in pathname :
214
+ name = pathname .split ("/" )[- 1 ]
215
+
216
+ if name == "" :
217
+ return html .H6 ("Please select an app from the dropdown" )
218
+
219
+ elif name in pages :
220
+ # return display_demo(
221
+ # name=name, layout=apps[name].layout, code=source_codes[name]
222
+ # )
223
+ return apps [name ].layout
224
+
225
+ else :
226
+ return notfound_404
227
+
228
+ return dash .no_update
229
+
230
+
231
+ if __name__ == "__main__" :
232
+ app .run_server (debug = True )
0 commit comments