basic-report 0.1.0__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.
Files changed (38) hide show
  1. basic_report/__init__.py +10 -0
  2. basic_report/configs/default.yaml +185 -0
  3. basic_report/css_and_scripts/bootstrap.min.css +7 -0
  4. basic_report/css_and_scripts/bootstrap.min.js +7 -0
  5. basic_report/css_and_scripts/dataTables.bootstrap4.min.js +8 -0
  6. basic_report/css_and_scripts/jquery-3.3.1.slim.min.js +2 -0
  7. basic_report/css_and_scripts/jquery.dataTables.min.js +166 -0
  8. basic_report/css_and_scripts/popper.min.js +5 -0
  9. basic_report/page.py +827 -0
  10. basic_report/report.py +404 -0
  11. basic_report/templates/accordion.css +20 -0
  12. basic_report/templates/accordion.html +20 -0
  13. basic_report/templates/base.html +59 -0
  14. basic_report/templates/colors.css +30 -0
  15. basic_report/templates/columns.html +9 -0
  16. basic_report/templates/global_links.html +7 -0
  17. basic_report/templates/header.html +5 -0
  18. basic_report/templates/image.html +16 -0
  19. basic_report/templates/list.html +7 -0
  20. basic_report/templates/navbar.css +17 -0
  21. basic_report/templates/navbar_left.html +32 -0
  22. basic_report/templates/navbar_right.html +32 -0
  23. basic_report/templates/navbar_top.html +25 -0
  24. basic_report/templates/plot.html +4 -0
  25. basic_report/templates/report_ball_section.html +80 -0
  26. basic_report/templates/report_header.html +6 -0
  27. basic_report/templates/site.css +5 -0
  28. basic_report/templates/sublevel.html +7 -0
  29. basic_report/templates/table.css +51 -0
  30. basic_report/templates/table.html +55 -0
  31. basic_report/templates/tabs.css +20 -0
  32. basic_report/templates/tabs.html +29 -0
  33. basic_report/templates/text.html +3 -0
  34. basic_report/utils.py +246 -0
  35. basic_report-0.1.0.dist-info/METADATA +194 -0
  36. basic_report-0.1.0.dist-info/RECORD +38 -0
  37. basic_report-0.1.0.dist-info/WHEEL +4 -0
  38. basic_report-0.1.0.dist-info/licenses/LICENSE.md +21 -0
basic_report/report.py ADDED
@@ -0,0 +1,404 @@
1
+ import datetime
2
+ import shutil
3
+ import yaml
4
+ from loguru import logger
5
+ from pandas import DataFrame
6
+ from pathlib import Path
7
+ from typing import Any, Union, Optional, Literal, Callable
8
+
9
+ from . import CONFIG_DIR, SCRIPTS_DIR
10
+ from .page import ReportPage
11
+ from .utils import default, verify_color_mode, ColorMap
12
+
13
+ #----------------------------------------------------------------------------------------------------------------------#
14
+ # region REPORT
15
+ #----------------------------------------------------------------------------------------------------------------------#
16
+ class Report:
17
+ """ Main report object """
18
+ def __init__(
19
+ self,
20
+ report_dir: Union[str, Path],
21
+ report_title: Optional[str]=None,
22
+ report_date: Optional[Union[str, int, datetime.date, datetime.datetime]]=None,
23
+ config_file: Optional[Union[str, Path]]=None,
24
+ color_mode: str='light',
25
+ text_color: Optional[str]=None,
26
+ ):
27
+ """ Initialize a new report
28
+
29
+ Args:
30
+ report_dir (Union[str, Path]): The main output dir which will contain the report
31
+ report_title (Optional[str]): The report title, if None is given it will be called `Unnamed Report`
32
+ report_date (Optional[Union[str, int, datetime.date, datetime.datetime]]): The report date, if none is given
33
+ today will be used
34
+ config_file (Optional[Union[str, Path]]): Path to the user config file that will be used. If none is given
35
+ the default config file will be read.
36
+ color_mode (str): Either `light` or `dark.
37
+ text_color (Optional[str]): Override for the text color of the report. For a more granular approach use the
38
+ config file
39
+ """
40
+ self.report_date = default(report_date, datetime.date.today())
41
+ self.report_title = default(report_title, 'Unnamed Report')
42
+ self.report_dir = Path(report_dir)
43
+ self.config = self._load_config(config_file)
44
+ self.color_map = ColorMap(self.config, color_mode, text_color)
45
+ self.color_mode = color_mode
46
+ self.text_color = text_color
47
+
48
+ self.pages: dict[str, ReportPage] = {}
49
+ self.add_page('main', subpage=False, color_mode=color_mode)
50
+ self.set_current_page('main')
51
+
52
+ self.global_links = []
53
+
54
+ #---------------#
55
+ # Magic Methods #
56
+ #---------------#
57
+ def __getitem__(self, key: str) -> ReportPage:
58
+ """ Simple access to the subpages
59
+
60
+ Args:
61
+ key (str): The page name to access
62
+
63
+ Returns:
64
+ ReportPage - The page object for the given page name
65
+ """
66
+ return self.pages[key]
67
+
68
+ #---------#
69
+ # Private #
70
+ #---------#
71
+ def _load_config(self, cfg_file: Optional[Union[str, Path]]=None) -> dict[str, Any]:
72
+ """ Load a report config file
73
+
74
+ Args:
75
+ cfg_file (Optional[Union[str, Path]]): The config file to load. If none is given the default config file
76
+ will be read
77
+
78
+ Returns:
79
+ dict[str, Any] - The config file content as dict
80
+ """
81
+ # Read default config
82
+ default_cfg_file = CONFIG_DIR / 'default.yaml'
83
+ if not default_cfg_file.exists():
84
+ msg = f'Could not find default config file {default_cfg_file}!'
85
+ logger.error(msg)
86
+ raise RuntimeError(msg)
87
+ with open(default_cfg_file, 'r') as f:
88
+ default_cfg = yaml.load(f, Loader=yaml.BaseLoader)
89
+
90
+ # Read custom config if given
91
+ if cfg_file is not None:
92
+ cfg_file = Path(cfg_file)
93
+ if not cfg_file.exists():
94
+ msg = f'Could not find default config file {default_cfg_file}!'
95
+ logger.error(msg)
96
+ raise RuntimeError(msg)
97
+ with open(cfg_file, 'r') as f:
98
+ custom_cfg = yaml.load(f, Loader=yaml.BaseLoader)
99
+ else:
100
+ custom_cfg = {}
101
+
102
+ cfg = default_cfg
103
+ for k,v in custom_cfg.items():
104
+ if k != 'custom_colors':
105
+ cfg[k] = v
106
+ else:
107
+ for ck,cv in v.items():
108
+ if ck not in cfg['custom_colors']:
109
+ cfg['custom_colors'][ck] = cv
110
+ else:
111
+ msg = (f'Tried to set color with name {ck}, which is already defined by the report class itself'
112
+ f'Please rename the color and try again.')
113
+ logger.error(msg)
114
+ raise RuntimeError(msg)
115
+
116
+ return cfg
117
+
118
+ #-----#
119
+ # API #
120
+ #-----#
121
+ # Report structure methods
122
+ # ------------------------
123
+ def add_page(self, name: str, subpage: bool=True, color_mode: Optional[str]=None):
124
+ """ Add a new page to the report
125
+
126
+ Args:
127
+ name (str): The name of the report page
128
+ subpage (bool): Indicates this is a subpage of the report
129
+ color_mode (Optional[str]): Color mode of the page, either `light` or `dark`
130
+ """
131
+ color_mode = verify_color_mode(default(color_mode, self.color_mode))
132
+ self.pages[name] = ReportPage(self.report_dir, name, self.config, self.color_map, subpage, color_mode)
133
+
134
+ def dump(self):
135
+ """ Save the report to file and add the CSS files """
136
+ # Dump all of the report pages
137
+ for page in self.pages.values():
138
+ if self.global_links:
139
+ page._make_global_link_navbar(self.global_links) #noqa: SLF001
140
+ page._dump() #noqa: SLF001
141
+
142
+ # Add the common css files and scripts from the package and the ones created by the color map
143
+ src = SCRIPTS_DIR
144
+ dst = self.report_dir / 'css_and_scripts'
145
+ shutil.copytree(src, dst)
146
+
147
+ self.color_map.make_css_files(dst)
148
+
149
+ def set_current_page(self, page_name: str):
150
+ """ Set the current page that is being worked on
151
+
152
+ Args:
153
+ page_name (str): The page you want to switch to
154
+ """
155
+ if page_name not in self.pages:
156
+ msg = f'Unkown page {page_name}. Known: {self.pages.keys()}'
157
+ logger.error(msg)
158
+ raise RuntimeError(msg)
159
+
160
+ self.current: ReportPage = self.pages[page_name]
161
+
162
+ # Headers & special sections
163
+ # --------------------------
164
+ def add_report_header(self, include_date: bool=True, include_created_at: bool=True, color: Optional[str]=None):
165
+ """ Add a report header to the current page
166
+
167
+ Args:
168
+ include_date (bool): Add the current date to the report
169
+ include_created_at (bool): Include a subheader with the exact creation time of the report
170
+ color (Optional[str]): The backgroound color for the header
171
+ """
172
+ self.current.add_report_header(self.report_title, self.report_date, include_date, include_created_at, color)
173
+
174
+ def add_error_warning_info_section(self, **kwargs):
175
+ """ Add a section detailing errors, warnings, and info """
176
+ self.current.add_error_warning_info_section(**kwargs)
177
+
178
+ def add_header(self, header_text: str, color: Optional[str]=None):
179
+ """ Add a header to the current page
180
+
181
+ Args:
182
+ header_text (str): Header text. You can use any valid HTML code inside the text, e.g., links and such.
183
+ color (Optional[str]): Color of the header box
184
+ """
185
+ self.current.add_header(header_text, color)
186
+
187
+ def add_sub_header(self, header_text: str, color: Optional[str]=None, sub_level: Optional[int]=None):
188
+ """ Add a sub header to the current page
189
+
190
+ Args:
191
+ header_text (str): Header text. You can use any valid HTML code inside the text, e.g., links and such.
192
+ color (Optional[str]): Color of the header box
193
+ sub_level (Optional[int]): Shrinks the width of the header box. Levels can be 1 to 5. The higher the level
194
+ the smaller the box
195
+ """
196
+ self.current.add_sub_header(header_text, color, sub_level)
197
+
198
+ # Miscellaneous elements
199
+ # ----------------------
200
+ def add_text(self, text: str, color: Optional[str]=None, align: Optional[Literal['left', 'center', 'right']]=None):
201
+ """ Add (colored) text to the page
202
+
203
+ This will create a new paragraph. So if you want to mix colors within
204
+ one paragraph you'll have to do this yourself for now
205
+
206
+ Args:
207
+ text (str): Your text
208
+ color (Optional[str]): One of the supported colors
209
+ align (Optional[Literal['left', 'center', 'right']]): Alignment of text
210
+ """
211
+ self.current.add_text(text, color, align)
212
+
213
+ def add_table(
214
+ self,
215
+ table_data: Union[DataFrame, list[list[Any]]],
216
+ formatters: Optional[Union[str, Callable, list[str], list[Callable]]]=None,
217
+ options: Optional[list[str]]=None,
218
+ size: Optional[int]=None,
219
+ align: Optional[Literal['left','center','right']]=None,
220
+ caption: Optional[str]=None,
221
+ footers: Optional[list[list[Any]]]=None,
222
+ order: Optional[list[list[Any]]]=None,
223
+ color: Optional[str]=None,
224
+ ):
225
+ """ Add a table to the page.
226
+
227
+ ``table_data`` can either be a pandas ``DataFrame`` or a list of lists.
228
+ For the former we assume indices and column labels are already properly
229
+ formatted. For the latter we assume the first item contains the headers.
230
+
231
+ Args:
232
+ table_data (Union[DataFrame, list[list[Any]]]):
233
+ Data to tabelize.
234
+
235
+ formatters (Optional[Union[str, Callable, list[str], list[Callable]]]):
236
+ Formatters applied to each table element. If a list is provided it
237
+ must match the number of columns. A single formatter is applied
238
+ to all columns. The default formatter is ``str()``.
239
+
240
+ options (Optional[list[str]]):
241
+ Turn optional elements of the DataTable on. Supported options:
242
+
243
+ - ``page`` - Split long tables into pages of 10 (configurable)
244
+ - ``info`` - Show number of rows
245
+ - ``search`` - Add a search field
246
+ - ``no_sort`` - Disable initial sorting
247
+ - ``color_negative_values`` - Highlight values < 0 in red
248
+ - ``color_positive_values`` - Highlight values > 0 in green
249
+ - ``full_width`` - Table spans full window width
250
+
251
+ size (Optional[int]):
252
+ Sets the width of the table (1-12). ``12`` uses full container width.
253
+
254
+ align (Optional[str]):
255
+ Alignment of cell content.
256
+
257
+ caption (Optional[str]):
258
+ Text shown below the table.
259
+
260
+ footers (Optional[list[list[Any]]]):
261
+ Rows always shown at the bottom of the table.
262
+
263
+ order (Optional[list[list[Any]]]):
264
+ Default table order in the form ``[[col_idx, 'asc' | 'desc'], ...]``.
265
+
266
+ color (Optional[str]):
267
+ Text color. Automatically determined if omitted.
268
+ """
269
+ self.current.add_table(table_data, formatters, options, size, align, caption, footers, order, color)
270
+
271
+ def add_list(self, list_items: list[str]):
272
+ """ Add a list to the current page
273
+
274
+ Args:
275
+ list_items (list[str]): A list to be added to the page
276
+ """
277
+ self.current.add_list(list_items)
278
+
279
+ def add_image(self,
280
+ image_source: Union[str, Path],
281
+ remove_source: Optional[bool]=None,
282
+ responsive: Optional[bool]=None,
283
+ center: Optional[bool]=None,
284
+ ):
285
+ """ Add an image to the current page
286
+
287
+ Args:
288
+ image_source (Union[str, Path]): The path to the image to add
289
+ remove_source (Optional[bool]): If true the source will be deleted and only acopy in the report dir is kept
290
+ responsive (Optional[bool]): If true the image will automatically be resized to match the outer container
291
+ center (Optional[bool]): If true the image will be centered
292
+ """
293
+ self.current.add_image(image_source, remove_source, responsive, center)
294
+
295
+ def add_local_link_to_page(self, page_name: str, link_name: str):
296
+ """ Add a link to another page to the current page
297
+
298
+ Args:
299
+ page_name (str): The key/name of the page to link to
300
+ link_name (str): The text that will be displayed
301
+ """
302
+ link = self.get_local_link_to_page(page_name, link_name)
303
+ self.current.add_local_link(link)
304
+
305
+ def get_local_link_to_page(self, page_name: str, link_name: str):
306
+ """ Get a link to another page to the current page
307
+
308
+ Args:
309
+ page_name (str): The key/name of the page to link to
310
+ link_name (str): The text that will be displayed
311
+ """
312
+ return self.pages[page_name].get_link_to_page(link_name)
313
+
314
+ def add_global_link_to_page(self, page_name: str, link_name: str):
315
+ """ Add a link to another page to the navbar
316
+
317
+ Args:
318
+ page_name (str): The key/name of the page to link to
319
+ link_name (str): The text that will be displayed
320
+ """
321
+ link = self.pages[page_name].get_link_to_page(link_name, for_navbar=True)
322
+ self.global_links.append(link)
323
+
324
+ def get_link_to_page(self, **kwargs):
325
+ """ Get a link to the current page """
326
+ self.current.get_link_to_page(**kwargs)
327
+
328
+ def add_custom_html_code(self, html: str):
329
+ """ Add custom / generic html code
330
+
331
+ Args:
332
+ html (str): The HTML string.
333
+ """
334
+ self.current.add_custom_html_code(html)
335
+
336
+ # Layouting
337
+ # ---------
338
+ def open_columns(self):
339
+ """ Start a column layout """
340
+ self.current.open_columns()
341
+
342
+ def add_column(self):
343
+ """ Add another column to the current column layout """
344
+ self.current.add_column()
345
+
346
+ def close_columns(self):
347
+ """ Close the current column layout """
348
+ self.current.close_columns()
349
+
350
+ def open_tabs(self):
351
+ """ Open a tab layout"""
352
+ self.current.open_tabs()
353
+
354
+ def add_tab(self, name: str):
355
+ """ Add another tab to the current tab layout
356
+
357
+ Args:
358
+ name (str): The text shown on the tab
359
+ """
360
+ self.current.add_tab(name)
361
+
362
+ def close_tabs(self):
363
+ """ Close the current tab layout"""
364
+ self.current.close_tabs()
365
+
366
+ def open_navbar(self, **kwargs):
367
+ """ Open a navbar layout """
368
+ self.current.open_navbar(**kwargs)
369
+
370
+ def add_navbar_item(self, name: str):
371
+ """ Add another navbar item to the current navbar layout
372
+
373
+ Args:
374
+ name (str): The text shown on the navbar pill
375
+ """
376
+ self.current.add_navbar_item(name)
377
+
378
+ def close_navbar(self):
379
+ """ Close the current navbar layout """
380
+ self.current.close_navbar()
381
+
382
+ def open_accordion(self, **kwargs):
383
+ """ Open a accordion layout"""
384
+ self.current.open_accordion(**kwargs)
385
+
386
+ def add_accordion_item(self, name: str):
387
+ """ Add another accordion item to the current accordion layout
388
+
389
+ Args:
390
+ name (str): The text shown on the accordion
391
+ """
392
+ self.current.add_accordion_item(name)
393
+
394
+ def close_accordion(self):
395
+ """ Close the current accorion layout """
396
+ self.current.close_accordion()
397
+
398
+ def open_sublevel(self, **kwargs):
399
+ """ Open a sub-level layout """
400
+ self.current.open_sublevel(**kwargs)
401
+
402
+ def close_sublevel(self):
403
+ """ Close the current sub-level layout """
404
+ self.current.close_sublevel()
@@ -0,0 +1,20 @@
1
+ .accordion .card {
2
+ background-color: {{ accordion_body_background_color }};
3
+ border: {{ accordion_border_size }} solid {{ accordion_border_color }};
4
+ }
5
+
6
+ /* Remove default header bottom border */
7
+ .accordion .card-header {
8
+ background-color: {{ accordion_header_background_color }};
9
+ border-bottom: none;
10
+ }
11
+
12
+ /* Control the border between header and body */
13
+ .accordion .card-body {
14
+ border-top: {{ accordion_body_top_border_size }} solid {{ accordion_body_top_border_color }}; /* ← your desired color */
15
+ color: {{ accordion_body_font_color }};
16
+ }
17
+
18
+ .accordion .btn-link {
19
+ color: {{ accordion_link_color }};
20
+ }
@@ -0,0 +1,20 @@
1
+ <div class="container p-1">
2
+ <div class="accordion" id="{{ accordion_id }}">
3
+ {% for item in accordion_items %}
4
+ <div class="card">
5
+ <div class="card-header">
6
+ <h5 class="mb-0">
7
+ <button class="btn btn-link collapsed w-100" type="button" data-toggle="collapse" data-target="#{{ item[1] }}">
8
+ {{ item[0] }}
9
+ </button>
10
+ </h5>
11
+ </div>
12
+ <div id="{{ item[1] }}" class="collapse" data-parent="#{{ accordion_id }}">
13
+ <div class="card-body">
14
+ {{ item[2] }}
15
+ </div>
16
+ </div>
17
+ </div>
18
+ {% endfor %}
19
+ </div>
20
+ </div>
@@ -0,0 +1,59 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <!-- META TAGS --!>
5
+ <meta charset="utf-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
7
+
8
+ <!-- BOOTSTRAP --!>
9
+ <link rel="stylesheet" href="css_and_scripts/bootstrap.min.css">
10
+
11
+ <!-- BOOTSTRAP - OPTIONAL JavaScript --!>
12
+ <script src="css_and_scripts/jquery-3.3.1.slim.min.js"></script>
13
+ <script src="css_and_scripts/popper.min.js"></script>
14
+ <script src="css_and_scripts/bootstrap.min.js"></script>
15
+
16
+ <!-- DATATABLES --!>
17
+ <script src="css_and_scripts/jquery.dataTables.min.js"></script>
18
+ <script src="css_and_scripts/dataTables.bootstrap4.min.js"></script>
19
+
20
+ <link rel="stylesheet" type="text/css" href="css_and_scripts/custom_colors.css" />
21
+ <link rel="stylesheet" type="text/css" href="css_and_scripts/tabs.css" />
22
+ <link rel="stylesheet" type="text/css" href="css_and_scripts/site.css" />
23
+ <link rel="stylesheet" type="text/css" href="css_and_scripts/accordion.css" />
24
+ <link rel="stylesheet" type="text/css" href="css_and_scripts/table.css" />
25
+ <link rel="stylesheet" type="text/css" href="css_and_scripts/navbar.css" />
26
+
27
+ {{ head_content }}
28
+
29
+ </head>
30
+
31
+ <style>
32
+ /* BOOTSTRAP HACK: fix content width inside hidden tabs */
33
+ .tab-content > .tab-pane:not(.active),
34
+ .pill-content > .pill-pane:not(.active) {
35
+ display: block;
36
+ height: 0;
37
+ overflow-y: hidden;
38
+ }
39
+
40
+ /* TABLE classes to highlight positive (negative) numbers in green (red) */
41
+ .positive {
42
+ color: #28a745bf;
43
+ }
44
+
45
+ .negative {
46
+ color: #dc3545bf;
47
+ }
48
+
49
+ /* TABLE class to separate footer from the rest of the table */
50
+ .double-top-border {
51
+ border-top: 3px double #c5c9c7;
52
+ }
53
+
54
+ </style>
55
+
56
+ <body>
57
+ {{ body_content }}
58
+ </body>
59
+ </html>
@@ -0,0 +1,30 @@
1
+ {% for color in color_items %}
2
+ /*------------------------------------
3
+ COLOR {{ color.name }}
4
+ ------------------------------------*/
5
+ .alert-{{ color.name }} {
6
+ color: {{ color.face_color }};
7
+ background-color: {{ color.font_and_background }};
8
+ {%- if color_mode == 'light' %}
9
+ border-color: {{ color.border_light }};
10
+ {%- else %}
11
+ border-color: {{ color.border_dark }};
12
+ {%- endif %}
13
+ border-width: 1px;
14
+ }
15
+
16
+ .alert-{{ color.name }} a {
17
+ color: {{ color.link }};
18
+ text-decoration: underline;
19
+ font-weight: 600;
20
+ }
21
+
22
+ .alert-{{ color.name }} a:hover {
23
+ color: {{ color.hover }};
24
+ text-decoration-thickness: 2px;
25
+ }
26
+
27
+ .text-{{ color.name }} {
28
+ color: {{ color.font_and_background }}
29
+ }
30
+ {% endfor %}
@@ -0,0 +1,9 @@
1
+ <div class="container p-1">
2
+ <div class="row">
3
+ {% for item in column_items %}
4
+ <div class="col">
5
+ {{ item[0] }}
6
+ </div>
7
+ {% endfor %}
8
+ </div>
9
+ </div>
@@ -0,0 +1,7 @@
1
+ <nav class="navbar sticky-top navbar-dark bg-dark navbar-expand-lg">
2
+ <ul class="navbar-nav">
3
+ {% for item in links %}
4
+ <li class="nav-item">{{ item }}</li>
5
+ {% endfor %}
6
+ </ul>
7
+ </nav>
@@ -0,0 +1,5 @@
1
+ <div class="container p-1 justify-content-center">
2
+ <div class="alert alert-{{ header_color }} text-center shadow-sm" role="alert">
3
+ <strong>{{ header_text }}</strong>
4
+ </div>
5
+ </div>
@@ -0,0 +1,16 @@
1
+ <div class="container p-1">
2
+ {% if center %}
3
+ <center>
4
+ {% endif %}
5
+ <a href="{{ source }}">
6
+ {% if responsive %}
7
+ <img src="{{ source }}" class="img-fluid">
8
+ {% else %}
9
+ <img src="{{ source }}">
10
+ {% endif %}
11
+ </a>
12
+ {% if center %}
13
+ </center>
14
+ {% endif %}
15
+ </div>
16
+
@@ -0,0 +1,7 @@
1
+ <div class="container">
2
+ <ul>
3
+ {% for item in list_items %}
4
+ <li>{{ item }}</li>
5
+ {% endfor %}
6
+ </ul>
7
+ </div>
@@ -0,0 +1,17 @@
1
+ {% for color in color_items %}
2
+ /*------------------------------------
3
+ COLOR {{ color.name }}
4
+ ------------------------------------*/
5
+ .nav-pills .pill-{{ color.name }} .nav-link:not(.active) {
6
+ background-color: {{ color.font_and_background }}51;
7
+ color: {{ color.font_and_background }};
8
+ font-weight: normal;
9
+ }
10
+
11
+
12
+ .nav-pills .pill-{{ color.name }} .nav-link {
13
+ background-color: {{ color.font_and_background }}bf;
14
+ color: {{ color.face_color }};
15
+ font-weight: bolder;
16
+ }
17
+ {% endfor %}
@@ -0,0 +1,32 @@
1
+ <div class="container p-2">
2
+ <div class="row">
3
+ <div class="col-2">
4
+ <ul class="nav nav-pills nav-stacked nav-fill" role="tablist">
5
+ {% for item in navbar_items %}
6
+ <li class="nav-item p-1 pill-{{ color }}">
7
+ {% if loop.index == 1 %}
8
+ <a class="nav-link active" data-toggle="pill" href="#{{ item[1] }}" role="tab">{{ item[0] }}</a>
9
+ {% else %}
10
+ <a class="nav-link" data-toggle="pill" href="#{{ item[1] }}" role="tab">{{ item[0] }}</a>
11
+ {% endif %}
12
+ </li>
13
+ {% endfor %}
14
+ </ul>
15
+ </div>
16
+ <div class = "col-10">
17
+ <div class="tab-content">
18
+ {% for item in navbar_items %}
19
+ {% if loop.index == 1 %}
20
+ <div class="tab-pane fade show active" id="{{ item[1] }}">
21
+ {% else %}
22
+ <div class="tab-pane fade" id="{{ item[1] }}">
23
+ {% endif %}
24
+ <div class="container">
25
+ {{ item[2] }}
26
+ </div>
27
+ </div>
28
+ {% endfor %}
29
+ </div>
30
+ </div>
31
+ </div>
32
+ </div>
@@ -0,0 +1,32 @@
1
+ <div class="container p-2">
2
+ <div class="row">
3
+ <div class = "col-10">
4
+ <div class="tab-content">
5
+ {% for item in navbar_items %}
6
+ {% if loop.index == 1 %}
7
+ <div class="tab-pane fade show active" id="{{ item[1] }}">
8
+ {% else %}
9
+ <div class="tab-pane fade" id="{{ item[1] }}">
10
+ {% endif %}
11
+ <div class="container">
12
+ {{ item[2] }}
13
+ </div>
14
+ </div>
15
+ {% endfor %}
16
+ </div>
17
+ </div>
18
+ <div class="col-2">
19
+ <ul class="nav nav-pills nav-stacked nav-fill" role="tablist">
20
+ {% for item in navbar_items %}
21
+ <li class="nav-item p-1 pill-{{ color }}">
22
+ {% if loop.index == 1 %}
23
+ <a class="nav-link active" data-toggle="pill" href="#{{ item[1] }}" role="tab">{{ item[0] }}</a>
24
+ {% else %}
25
+ <a class="nav-link" data-toggle="pill" href="#{{ item[1] }}" role="tab">{{ item[0] }}</a>
26
+ {% endif %}
27
+ </li>
28
+ {% endfor %}
29
+ </ul>
30
+ </div>
31
+ </div>
32
+ </div>