dashblox 0.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dashblox/__init__.py +11 -0
- dashblox/_version.py +1 -0
- dashblox/components/__init__.py +3 -0
- dashblox/components/loading_message.py +33 -0
- dashblox/components/table.py +237 -0
- dashblox/components/tabs.py +99 -0
- dashblox/run/__init__.py +6 -0
- dashblox/run/__main__.py +8 -0
- dashblox/run/app_layout.py +112 -0
- dashblox/run/callback_utils.py +196 -0
- dashblox/run/get_homepage_layout.py +65 -0
- dashblox/run/multipage_setup.py +149 -0
- dashblox/run/notebook_test.py +67 -0
- dashblox/run/set_trigger.py +16 -0
- dashblox/run/set_up_page.py +217 -0
- dashblox/utils/__init__.py +2 -0
- dashblox/utils/get_user_id.py +21 -0
- dashblox/utils/module_utils.py +66 -0
- dashblox-0.0.2.dist-info/METADATA +16 -0
- dashblox-0.0.2.dist-info/RECORD +23 -0
- dashblox-0.0.2.dist-info/WHEEL +5 -0
- dashblox-0.0.2.dist-info/licenses/LICENSE +21 -0
- dashblox-0.0.2.dist-info/top_level.txt +1 -0
dashblox/__init__.py
ADDED
dashblox/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.2"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from dash import html
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def loading_message(id='loading_message',
|
|
5
|
+
message_text='Loading Data...',
|
|
6
|
+
initially_hidden=False):
|
|
7
|
+
"""
|
|
8
|
+
Return layout component for a loading message to prevent user access to content
|
|
9
|
+
while data is loading; can be toggled off by setting "hidden" property to True
|
|
10
|
+
"""
|
|
11
|
+
return html.Div(id=id,
|
|
12
|
+
children=[
|
|
13
|
+
html.H4(children=message_text,
|
|
14
|
+
style={'position': 'absolute',
|
|
15
|
+
'top': '200px',
|
|
16
|
+
'height': '120px',
|
|
17
|
+
'left': '300px',
|
|
18
|
+
'width': '600px',
|
|
19
|
+
'padding': '45px 215px',
|
|
20
|
+
'color': 'rgba(19, 20, 23, 1)',
|
|
21
|
+
'background-color': 'rgba(255, 255, 255, 1)',
|
|
22
|
+
'z-index': '99'}
|
|
23
|
+
)
|
|
24
|
+
],
|
|
25
|
+
hidden=initially_hidden,
|
|
26
|
+
style={'position': 'fixed',
|
|
27
|
+
'top': '0px',
|
|
28
|
+
'bottom': '0px',
|
|
29
|
+
'left': '0px',
|
|
30
|
+
'right': '0px',
|
|
31
|
+
'background-color': 'rgba(0, 0, 0, 0.3)',
|
|
32
|
+
'z-index': '98'})
|
|
33
|
+
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
from collections import OrderedDict
|
|
3
|
+
from dash import html, dash_table
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def table(*args, **kwargs):
|
|
7
|
+
"""
|
|
8
|
+
Generate and return layout component for data table based on input parameters
|
|
9
|
+
"""
|
|
10
|
+
return Table(*args, **kwargs).component
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Table(object):
|
|
14
|
+
default_style = {
|
|
15
|
+
'div': {
|
|
16
|
+
'position': 'absolute',
|
|
17
|
+
'top': '0px',
|
|
18
|
+
'bottom': '0px',
|
|
19
|
+
'left': '0px',
|
|
20
|
+
'overflow-x': 'auto',
|
|
21
|
+
'overflow-y': 'auto',
|
|
22
|
+
},
|
|
23
|
+
'body_cell': {
|
|
24
|
+
'font-size': '14px',
|
|
25
|
+
'font-family': ['Arial', 'sans-serif'],
|
|
26
|
+
'text-align': 'left',
|
|
27
|
+
'background-color': '#FFFFFF',
|
|
28
|
+
'color': '#131417',
|
|
29
|
+
'box-sizing': 'border-box',
|
|
30
|
+
'padding': '4px 6px',
|
|
31
|
+
'overflow': 'hidden',
|
|
32
|
+
'text-overflow': 'clip',
|
|
33
|
+
'border-left': '1pt solid #737373',
|
|
34
|
+
'border-right': '1pt solid #737373',
|
|
35
|
+
'border-bottom': '1pt solid #737373',
|
|
36
|
+
},
|
|
37
|
+
'header_cell': {
|
|
38
|
+
'font-size': '14px',
|
|
39
|
+
'font-family': ['Arial', 'sans-serif'],
|
|
40
|
+
'font-weight': 'bold',
|
|
41
|
+
'text-align': 'left',
|
|
42
|
+
'background-color': '#02808A',
|
|
43
|
+
'color': '#FFFFFF',
|
|
44
|
+
'box-sizing': 'border-box',
|
|
45
|
+
'padding': '4px 6px',
|
|
46
|
+
'overflow': 'hidden',
|
|
47
|
+
'text-overflow': 'clip',
|
|
48
|
+
'border': '1pt solid #FFFFFF',
|
|
49
|
+
},
|
|
50
|
+
'header_row': {
|
|
51
|
+
'height': '30px',
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
def __init__(self, table_id, columns, **kwargs):
|
|
56
|
+
"""
|
|
57
|
+
Create combined header/DataTable component for dashboard layout from columns list
|
|
58
|
+
"""
|
|
59
|
+
columns_cfg = self.get_columns_cfg(columns)
|
|
60
|
+
apply_style = self.setup_styles(columns_cfg, kwargs)
|
|
61
|
+
col_defs, table_editable_flag = self.setup_columns(columns_cfg)
|
|
62
|
+
cond_style = self.get_conditional_styles(columns_cfg)
|
|
63
|
+
tot_width = sum([i['width'] for i in columns_cfg.values()])
|
|
64
|
+
|
|
65
|
+
self._component = html.Div(
|
|
66
|
+
[
|
|
67
|
+
html.Table(
|
|
68
|
+
self.get_head_components(columns_cfg, apply_style),
|
|
69
|
+
style={
|
|
70
|
+
'border-collapse': 'separate',
|
|
71
|
+
'border-spacing': '0px',
|
|
72
|
+
'position': 'sticky',
|
|
73
|
+
'top': '0px',
|
|
74
|
+
'z-index': '1',
|
|
75
|
+
}
|
|
76
|
+
),
|
|
77
|
+
dash_table.DataTable(id=table_id,
|
|
78
|
+
columns=col_defs,
|
|
79
|
+
data=[],
|
|
80
|
+
page_action='none',
|
|
81
|
+
style_table={'width': f'{tot_width}px',
|
|
82
|
+
'z-index': '0'},
|
|
83
|
+
style_data=apply_style['body_cell'],
|
|
84
|
+
style_data_conditional=cond_style,
|
|
85
|
+
css=[
|
|
86
|
+
{'selector': 'tr:first-child', 'rule': 'display: none'},
|
|
87
|
+
{'selector': '.dash-spreadsheet tr', 'rule': 'height: 10px;'}
|
|
88
|
+
],
|
|
89
|
+
editable=table_editable_flag,
|
|
90
|
+
cell_selectable=kwargs.get('selectable', False),
|
|
91
|
+
selected_cells=[])
|
|
92
|
+
],
|
|
93
|
+
style=apply_style['div']
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def get_columns_cfg(columns):
|
|
98
|
+
"""
|
|
99
|
+
Make OrderedDict from columns parameter
|
|
100
|
+
"""
|
|
101
|
+
columns_cfg = OrderedDict([(k.lower(), v) for k,v in columns])
|
|
102
|
+
for k in columns_cfg:
|
|
103
|
+
if 'width' not in columns_cfg[k]:
|
|
104
|
+
columns_cfg[k]['width'] = 100
|
|
105
|
+
return columns_cfg
|
|
106
|
+
|
|
107
|
+
def setup_styles(self, columns_cfg, kwargs):
|
|
108
|
+
"""
|
|
109
|
+
Define styles dicts for Div/DataTable objects from default+kwargs
|
|
110
|
+
"""
|
|
111
|
+
apply_style = copy.deepcopy(self.default_style)
|
|
112
|
+
apply_style['div'].update(kwargs.get('style', {}))
|
|
113
|
+
if apply_style['div'].get('right') is None:
|
|
114
|
+
w = sum([i['width'] for i in columns_cfg.values()]) + 18
|
|
115
|
+
apply_style['div']['width'] = f'{w}px'
|
|
116
|
+
|
|
117
|
+
for k in {*apply_style}-{'div'}:
|
|
118
|
+
apply_style[k].update(kwargs.get(f'{k}_style', {}))
|
|
119
|
+
|
|
120
|
+
return apply_style
|
|
121
|
+
|
|
122
|
+
def setup_columns(self, columns_cfg):
|
|
123
|
+
"""
|
|
124
|
+
Generate column definitions input for dash_table.DataTable
|
|
125
|
+
"""
|
|
126
|
+
col_defs = list()
|
|
127
|
+
table_editable_flag = False
|
|
128
|
+
for c in columns_cfg:
|
|
129
|
+
col_def = {'id': c, 'name': c}
|
|
130
|
+
if (num_fmt:=columns_cfg[c].get('num_fmt')):
|
|
131
|
+
col_def['type'] = 'numeric'
|
|
132
|
+
if isinstance(num_fmt, str):
|
|
133
|
+
col_def['format'] = self.get_num_fmt(num_fmt)
|
|
134
|
+
else:
|
|
135
|
+
col_def['format'] = num_fmt
|
|
136
|
+
if columns_cfg[c].get('editable', False) and not (columns_cfg[c]['width'] == 0):
|
|
137
|
+
col_def['editable'] = True
|
|
138
|
+
table_editable_flag = True
|
|
139
|
+
col_defs += [col_def]
|
|
140
|
+
|
|
141
|
+
return col_defs, table_editable_flag
|
|
142
|
+
|
|
143
|
+
@staticmethod
|
|
144
|
+
def get_num_fmt(num_fmt):
|
|
145
|
+
"""
|
|
146
|
+
Converts number format string into dash_table.Format or
|
|
147
|
+
dash_table.FormatTemplate object to set column format for table.
|
|
148
|
+
"""
|
|
149
|
+
t,p = num_fmt.split('-')
|
|
150
|
+
p = int(p)
|
|
151
|
+
return {
|
|
152
|
+
'money': dash_table.FormatTemplate.money(p).group(True),
|
|
153
|
+
'comma': dash_table.Format.Format(precision=p, group=True, groups=[3]),
|
|
154
|
+
'number': dash_table.Format.Format(precision=p,
|
|
155
|
+
scheme=dash_table.Format.Scheme.fixed),
|
|
156
|
+
'pct': dash_table.FormatTemplate.percentage(p),
|
|
157
|
+
}[t]
|
|
158
|
+
|
|
159
|
+
@staticmethod
|
|
160
|
+
def get_conditional_styles(columns_cfg):
|
|
161
|
+
"""
|
|
162
|
+
Create conditional style dict, populate rules for column widths,
|
|
163
|
+
number alignment and any conditional formatting specified in layout.
|
|
164
|
+
"""
|
|
165
|
+
cond_style = {c: dict() for c in columns_cfg}
|
|
166
|
+
for c in columns_cfg:
|
|
167
|
+
if columns_cfg[c]['width'] == 0:
|
|
168
|
+
cond_style[c]['display'] = 'none'
|
|
169
|
+
else:
|
|
170
|
+
w = columns_cfg[c]['width']
|
|
171
|
+
for p in ['min-width', 'max-width']:
|
|
172
|
+
cond_style[c][p] = f'{w}px'
|
|
173
|
+
cond_style[c]['text-align'] = ['left','right']['num_fmt' in columns_cfg[c]]
|
|
174
|
+
cond_style = [{'if': {'column_id': c}, **cond_style[c]}
|
|
175
|
+
for c in columns_cfg]
|
|
176
|
+
|
|
177
|
+
for c,cfg in columns_cfg.items():
|
|
178
|
+
for query,style in cfg.get('cstyle', []):
|
|
179
|
+
cond_style += [{'if': {'filter_query': query,
|
|
180
|
+
'column_id': c},
|
|
181
|
+
**style}]
|
|
182
|
+
|
|
183
|
+
return cond_style
|
|
184
|
+
|
|
185
|
+
@staticmethod
|
|
186
|
+
def get_head_components(columns_cfg, apply_style):
|
|
187
|
+
"""
|
|
188
|
+
Return table head (html.Thead) object for table
|
|
189
|
+
"""
|
|
190
|
+
# Organize column labels; detect where multi-row head needed
|
|
191
|
+
hlabels = OrderedDict([(k, z if isinstance((z:=v.get('label', k)), list) else [z])
|
|
192
|
+
for k,v in columns_cfg.items()
|
|
193
|
+
if v['width'] > 0])
|
|
194
|
+
n_hrows = max([len(i) for i in hlabels.values()])
|
|
195
|
+
|
|
196
|
+
hlabels_grid = [[*hlabels[k], *['']*n_hrows][:n_hrows] for k in hlabels]
|
|
197
|
+
row_spans = [*zip(*[[[0,cm[i+1:].index(1)+1][j]
|
|
198
|
+
for i,j in enumerate(cm[:-1])]
|
|
199
|
+
for cl in hlabels_grid
|
|
200
|
+
for cm in [[min(len(i),1) for i in cl+['X']]]])]
|
|
201
|
+
|
|
202
|
+
hlabels_grid = [*zip(*hlabels_grid)]
|
|
203
|
+
col_spans = [[[0,rm[i+1:].index(1)+1][j] for i,j in enumerate(rm[:-1])]
|
|
204
|
+
for rl in hlabels_grid
|
|
205
|
+
for rm in [[int(i==0 or j!=rl[i-1])
|
|
206
|
+
for i,j in enumerate([*rl,'X'])]]]
|
|
207
|
+
|
|
208
|
+
# Generate head components, with expanded cells where needed
|
|
209
|
+
head_components = []
|
|
210
|
+
for r in range(n_hrows):
|
|
211
|
+
row = []
|
|
212
|
+
for c,cid in enumerate(columns_cfg):
|
|
213
|
+
if (sp_r:=row_spans[r][c])*(sp_c:=col_spans[r][c]) != 0:
|
|
214
|
+
cell_style = copy.deepcopy(apply_style['header_cell'])
|
|
215
|
+
if sp_c == 1:
|
|
216
|
+
cell_style.update({i: f"{columns_cfg[cid]['width']}px"
|
|
217
|
+
for i in ['min-width','max-width']})
|
|
218
|
+
cell_style.update({
|
|
219
|
+
'border-top-color': (cch:=[cell_style[i]
|
|
220
|
+
for i in ['color',
|
|
221
|
+
'background-color']])[r==0],
|
|
222
|
+
'border-bottom-color': cch[r+sp_r==n_hrows],
|
|
223
|
+
'border-left-color': cch[c==0],
|
|
224
|
+
'border-right-color': cch[c+sp_c==len(columns_cfg)],
|
|
225
|
+
})
|
|
226
|
+
row += [html.Th(children=hlabels_grid[r][c],
|
|
227
|
+
rowSpan=sp_r,
|
|
228
|
+
colSpan=sp_c,
|
|
229
|
+
style=cell_style)]
|
|
230
|
+
head_components += [html.Tr(row, style=apply_style['header_row'])]
|
|
231
|
+
|
|
232
|
+
return [html.Thead(head_components)]
|
|
233
|
+
|
|
234
|
+
@property
|
|
235
|
+
def component(self):
|
|
236
|
+
return self._component
|
|
237
|
+
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from dash import dcc, html
|
|
2
|
+
import copy
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def tabs(*args, **kwargs):
|
|
6
|
+
"""
|
|
7
|
+
Generate and return layout component for tabs object based on input parameters
|
|
8
|
+
"""
|
|
9
|
+
return Tabs(*args, **kwargs).component
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Tabs(object):
|
|
13
|
+
default_style = {
|
|
14
|
+
'div': {
|
|
15
|
+
'position': 'absolute',
|
|
16
|
+
'top': '12px',
|
|
17
|
+
'bottom': '12px',
|
|
18
|
+
'left': '12px',
|
|
19
|
+
'right': '12px',
|
|
20
|
+
},
|
|
21
|
+
'selector': {
|
|
22
|
+
'height': '44px',
|
|
23
|
+
'width': '540px',
|
|
24
|
+
},
|
|
25
|
+
'tab': {
|
|
26
|
+
'border-top-left-radius': '3px',
|
|
27
|
+
'border-top-right-radius': '3px',
|
|
28
|
+
'border-bottom': '1px solid #D6D6D6',
|
|
29
|
+
'border-top': '1px solid #D6D6D6',
|
|
30
|
+
'padding': '6px',
|
|
31
|
+
'font-weight': 'normal',
|
|
32
|
+
'color': '#131417',
|
|
33
|
+
'backgroundColor': '#D6D6D6',
|
|
34
|
+
},
|
|
35
|
+
'selected_tab': {
|
|
36
|
+
'border-top-left-radius': '3px',
|
|
37
|
+
'border-top-right-radius': '3px',
|
|
38
|
+
'border-bottom': '1px solid #D6D6D6',
|
|
39
|
+
'border-top': '2px solid #02808A',
|
|
40
|
+
'padding': '6px',
|
|
41
|
+
'font-weight': 'bold',
|
|
42
|
+
'color': '#02808A',
|
|
43
|
+
'backgroundColor': '#FFFFFF',
|
|
44
|
+
},
|
|
45
|
+
'tab_div': {
|
|
46
|
+
'position': 'absolute',
|
|
47
|
+
'top': '48px',
|
|
48
|
+
'bottom': '0px',
|
|
49
|
+
'left': '0px',
|
|
50
|
+
'right': '0px',
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
def __init__(self,
|
|
55
|
+
tabs_id,
|
|
56
|
+
tabs,
|
|
57
|
+
**kwargs):
|
|
58
|
+
"""
|
|
59
|
+
Create dcc.Tabs component for dashboard layout from input list
|
|
60
|
+
"""
|
|
61
|
+
self.apply_style = self.setup_styles(kwargs)
|
|
62
|
+
self._component = html.Div(
|
|
63
|
+
[
|
|
64
|
+
dcc.Tabs(id=tabs_id,
|
|
65
|
+
value=tabs[kwargs.get('initial_select', 0)]['tab_id'],
|
|
66
|
+
children=[self.get_tab(**t) for t in tabs],
|
|
67
|
+
style=self.apply_style['selector'])
|
|
68
|
+
],
|
|
69
|
+
style=self.apply_style['div']
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def setup_styles(self, kwargs):
|
|
73
|
+
"""
|
|
74
|
+
Define styles dicts for Div/Tab/Tabs objects from default+kwargs
|
|
75
|
+
"""
|
|
76
|
+
apply_style = copy.deepcopy(self.default_style)
|
|
77
|
+
apply_style['div'].update(kwargs.get('style', {}))
|
|
78
|
+
for k in {*apply_style}-{'div'}:
|
|
79
|
+
apply_style[k].update(kwargs.get(f'{k}_style', {}))
|
|
80
|
+
# (Auto-adjust tab_div/top to match selector/height)
|
|
81
|
+
top_px = int(apply_style['selector']['height'].replace('px', '')) + 4
|
|
82
|
+
apply_style['tab_div']['top'] = f"{top_px}px"
|
|
83
|
+
|
|
84
|
+
return apply_style
|
|
85
|
+
|
|
86
|
+
def get_tab(self, tab_id, label, content):
|
|
87
|
+
"""
|
|
88
|
+
Return layout component for tab content.
|
|
89
|
+
"""
|
|
90
|
+
return dcc.Tab(label=label,
|
|
91
|
+
value=tab_id,
|
|
92
|
+
children=html.Div(content,
|
|
93
|
+
style=self.apply_style['tab_div']),
|
|
94
|
+
style=self.apply_style['tab'],
|
|
95
|
+
selected_style=self.apply_style['selected_tab'])
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def component(self):
|
|
99
|
+
return self._component
|
dashblox/run/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
from .multipage_setup import multipage_setup, set_host_and_port
|
|
2
|
+
from .app_layout import app_layout
|
|
3
|
+
from .set_up_page import set_up_page
|
|
4
|
+
from .callback_utils import DashCallbackMgr, SourceData
|
|
5
|
+
from .set_trigger import set_trigger
|
|
6
|
+
from .notebook_test import notebook_test
|
dashblox/run/__main__.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from dash import html, dcc, page_registry, page_container
|
|
2
|
+
import dash_bootstrap_components as dbc
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def app_layout(title=None):
|
|
6
|
+
"""
|
|
7
|
+
Return layout for multi-page app
|
|
8
|
+
"""
|
|
9
|
+
# Fix position of page container to sit under navbar
|
|
10
|
+
cntix = [i for i, j in enumerate(page_container.children)
|
|
11
|
+
if j.id == '_pages_content'][0]
|
|
12
|
+
page_container.children[cntix].style = {'position': 'absolute',
|
|
13
|
+
'top': '50px',
|
|
14
|
+
'bottom': '0px',
|
|
15
|
+
'left': '0px',
|
|
16
|
+
'right': '0px'}
|
|
17
|
+
return html.Div(
|
|
18
|
+
[
|
|
19
|
+
create_navbar(title=title or ''),
|
|
20
|
+
page_container
|
|
21
|
+
]
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
logo = f'./assets/luna_logo_teal.png'
|
|
26
|
+
|
|
27
|
+
lnk_style = {
|
|
28
|
+
'width': '128px',
|
|
29
|
+
'height': '36px',
|
|
30
|
+
'margin': '0px 5px',
|
|
31
|
+
'color': 'white',
|
|
32
|
+
'border': '2px solid white',
|
|
33
|
+
'font-size': '14px',
|
|
34
|
+
'font-weight': 'normal',
|
|
35
|
+
'text-align': 'center',
|
|
36
|
+
'vertical-align': 'middle',
|
|
37
|
+
'line-height': '32px'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def create_navbar(title):
|
|
42
|
+
"""
|
|
43
|
+
Return layout components for top-of-page navigation bar.
|
|
44
|
+
"""
|
|
45
|
+
navbar = dbc.Navbar(
|
|
46
|
+
[
|
|
47
|
+
html.A(html.Img(src=logo,
|
|
48
|
+
height=48),
|
|
49
|
+
href='/',
|
|
50
|
+
style = {
|
|
51
|
+
'position': 'fixed',
|
|
52
|
+
'vertical-align': 'middle',
|
|
53
|
+
'left': '20px',
|
|
54
|
+
}
|
|
55
|
+
),
|
|
56
|
+
html.A(html.Div(title,
|
|
57
|
+
style = {
|
|
58
|
+
'font-size': '20px',
|
|
59
|
+
'font-weight': 'bold',
|
|
60
|
+
'color': '#014A50',
|
|
61
|
+
}
|
|
62
|
+
),
|
|
63
|
+
href='/',
|
|
64
|
+
style = {
|
|
65
|
+
'position': 'fixed',
|
|
66
|
+
'vertical-align': 'middle',
|
|
67
|
+
'left': '80px',
|
|
68
|
+
'textDecoration': 'none',
|
|
69
|
+
}
|
|
70
|
+
),
|
|
71
|
+
html.A(html.Div(title,
|
|
72
|
+
style = {
|
|
73
|
+
'font-size': '20px',
|
|
74
|
+
'font-weight': 'bold',
|
|
75
|
+
'color': '#014A50',
|
|
76
|
+
}
|
|
77
|
+
),
|
|
78
|
+
href='/',
|
|
79
|
+
style = {
|
|
80
|
+
'position': 'fixed',
|
|
81
|
+
'vertical-align': 'middle',
|
|
82
|
+
'left': '80px',
|
|
83
|
+
'textDecoration': 'none',
|
|
84
|
+
}
|
|
85
|
+
),
|
|
86
|
+
html.A(html.Img(src=logo,
|
|
87
|
+
height=36),
|
|
88
|
+
href='https://www.google.com',
|
|
89
|
+
target='_blank',
|
|
90
|
+
style = {
|
|
91
|
+
'position': 'fixed',
|
|
92
|
+
'vertical-align': 'middle',
|
|
93
|
+
'right': '20px',
|
|
94
|
+
}
|
|
95
|
+
),
|
|
96
|
+
],
|
|
97
|
+
sticky='top',
|
|
98
|
+
style={
|
|
99
|
+
'position': 'fixed',
|
|
100
|
+
'top': '0px',
|
|
101
|
+
'left': '0px',
|
|
102
|
+
'height': '50px',
|
|
103
|
+
'right': '0px',
|
|
104
|
+
'font-family': ['Open Sans', 'sans-serif'],
|
|
105
|
+
'background-image': 'url("./assets/navbar.png")',
|
|
106
|
+
'background-size': 'cover',
|
|
107
|
+
'min-height': '50px',
|
|
108
|
+
'background-color': 'transparent',
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return navbar
|