staticdash 0.3.1__tar.gz → 0.3.3__tar.gz
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.
- {staticdash-0.3.1 → staticdash-0.3.3}/PKG-INFO +1 -1
- {staticdash-0.3.1 → staticdash-0.3.3}/pyproject.toml +1 -1
- {staticdash-0.3.1 → staticdash-0.3.3}/staticdash/assets/css/style.css +80 -1
- {staticdash-0.3.1 → staticdash-0.3.3}/staticdash/assets/js/script.js +35 -3
- staticdash-0.3.3/staticdash/dashboard.py +208 -0
- {staticdash-0.3.1 → staticdash-0.3.3}/staticdash.egg-info/PKG-INFO +1 -1
- staticdash-0.3.1/staticdash/dashboard.py +0 -205
- {staticdash-0.3.1 → staticdash-0.3.3}/README.md +0 -0
- {staticdash-0.3.1 → staticdash-0.3.3}/setup.cfg +0 -0
- {staticdash-0.3.1 → staticdash-0.3.3}/staticdash/__init__.py +0 -0
- {staticdash-0.3.1 → staticdash-0.3.3}/staticdash.egg-info/SOURCES.txt +0 -0
- {staticdash-0.3.1 → staticdash-0.3.3}/staticdash.egg-info/dependency_links.txt +0 -0
- {staticdash-0.3.1 → staticdash-0.3.3}/staticdash.egg-info/requires.txt +0 -0
- {staticdash-0.3.1 → staticdash-0.3.3}/staticdash.egg-info/top_level.txt +0 -0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "staticdash"
|
7
|
-
version = "0.3.
|
7
|
+
version = "0.3.3"
|
8
8
|
description = "A lightweight static HTML dashboard generator with Plotly and pandas support."
|
9
9
|
authors = [
|
10
10
|
{ name = "Brian Day", email = "brian.day1@gmail.com" }
|
@@ -104,12 +104,14 @@ body {
|
|
104
104
|
height: auto !important;
|
105
105
|
}
|
106
106
|
|
107
|
+
/* Tables */
|
107
108
|
table {
|
108
109
|
width: 100%;
|
109
110
|
border-collapse: collapse;
|
110
111
|
margin-top: 1em;
|
111
112
|
background-color: white;
|
112
113
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
114
|
+
overflow-x: auto;
|
113
115
|
}
|
114
116
|
|
115
117
|
thead {
|
@@ -153,6 +155,7 @@ tbody tr:hover {
|
|
153
155
|
background-color: #547a9f;
|
154
156
|
}
|
155
157
|
|
158
|
+
/* Sortable Tables */
|
156
159
|
table.sortable th {
|
157
160
|
cursor: pointer;
|
158
161
|
position: relative;
|
@@ -176,13 +179,89 @@ table.sortable th.sorted-desc::after {
|
|
176
179
|
content: "\25BC";
|
177
180
|
}
|
178
181
|
|
182
|
+
/* MiniPage Layout */
|
179
183
|
.minipage-row {
|
180
184
|
display: flex;
|
185
|
+
width: 100%;
|
181
186
|
gap: 20px;
|
182
187
|
margin-bottom: 20px;
|
188
|
+
box-sizing: border-box;
|
183
189
|
}
|
184
190
|
|
185
|
-
.minipage {
|
191
|
+
.minipage-cell {
|
192
|
+
flex: 1 1 0;
|
193
|
+
min-width: 0;
|
186
194
|
box-sizing: border-box;
|
187
195
|
padding: 10px;
|
196
|
+
overflow-x: auto;
|
197
|
+
}
|
198
|
+
|
199
|
+
.minipage-cell pre {
|
200
|
+
flex: 0 1 auto;
|
201
|
+
max-height: 400px;
|
202
|
+
overflow: auto;
|
203
|
+
}
|
204
|
+
|
205
|
+
.minipage-cell h4 {
|
206
|
+
margin-top: 0;
|
207
|
+
}
|
208
|
+
|
209
|
+
.minipage-cell .syntax-block {
|
210
|
+
margin-top: 0.5em;
|
211
|
+
margin-bottom: 0.5em;
|
212
|
+
}
|
213
|
+
|
214
|
+
.minipage-cell table {
|
215
|
+
display: block;
|
216
|
+
overflow-x: auto;
|
217
|
+
width: 100%;
|
218
|
+
}
|
219
|
+
|
220
|
+
/* Syntax Blocks */
|
221
|
+
.syntax-block {
|
222
|
+
position: relative;
|
223
|
+
margin: 1em 0;
|
224
|
+
background: #23272e;
|
225
|
+
border-radius: 6px;
|
226
|
+
overflow: auto;
|
227
|
+
color: #f8f8f2;
|
228
|
+
}
|
229
|
+
|
230
|
+
.syntax-block pre {
|
231
|
+
margin: 0;
|
232
|
+
padding: 1em;
|
233
|
+
background: none;
|
234
|
+
color: inherit;
|
235
|
+
font-family: 'Fira Mono', 'Consolas', monospace;
|
236
|
+
font-size: 0.95em;
|
237
|
+
}
|
238
|
+
|
239
|
+
.code-toolbar {
|
240
|
+
position: absolute;
|
241
|
+
top: 6px;
|
242
|
+
right: 12px;
|
243
|
+
z-index: 2;
|
244
|
+
}
|
245
|
+
|
246
|
+
.copy-btn,
|
247
|
+
.view-raw-btn {
|
248
|
+
color: #fff;
|
249
|
+
background: #444;
|
250
|
+
border-radius: 4px;
|
251
|
+
padding: 2px 8px;
|
252
|
+
font-size: 0.85em;
|
253
|
+
text-decoration: none;
|
254
|
+
margin-left: 4px;
|
255
|
+
cursor: pointer;
|
256
|
+
transition: background 0.2s;
|
257
|
+
}
|
258
|
+
|
259
|
+
.copy-btn:hover,
|
260
|
+
.view-raw-btn:hover {
|
261
|
+
background: #1abc9c;
|
262
|
+
}
|
263
|
+
|
264
|
+
.table-wrapper {
|
265
|
+
overflow-x: auto;
|
266
|
+
width: 100%;
|
188
267
|
}
|
@@ -11,9 +11,9 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
11
11
|
});
|
12
12
|
}
|
13
13
|
|
14
|
-
function showPage(
|
14
|
+
function showPage(pageId) {
|
15
15
|
sections.forEach(section => section.style.display = "none");
|
16
|
-
const page = document.getElementById(
|
16
|
+
const page = document.getElementById(pageId);
|
17
17
|
if (page) {
|
18
18
|
page.style.display = "block";
|
19
19
|
|
@@ -24,8 +24,12 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
24
24
|
}
|
25
25
|
|
26
26
|
links.forEach(link => link.classList.remove("active"));
|
27
|
-
const activeLink = Array.from(links).find(link => link.dataset.target ===
|
27
|
+
const activeLink = Array.from(links).find(link => link.dataset.target === pageId);
|
28
28
|
if (activeLink) activeLink.classList.add("active");
|
29
|
+
|
30
|
+
if (window.Prism && typeof Prism.highlightAll === "function") {
|
31
|
+
Prism.highlightAll();
|
32
|
+
}
|
29
33
|
}
|
30
34
|
|
31
35
|
links.forEach(link => {
|
@@ -91,4 +95,32 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
91
95
|
});
|
92
96
|
});
|
93
97
|
});
|
98
|
+
|
99
|
+
// Syntax block copy/view raw
|
100
|
+
document.querySelectorAll(".copy-btn").forEach(btn => {
|
101
|
+
btn.addEventListener("click", e => {
|
102
|
+
e.preventDefault();
|
103
|
+
const codeId = btn.dataset.target;
|
104
|
+
const code = document.getElementById(codeId);
|
105
|
+
if (code) {
|
106
|
+
navigator.clipboard.writeText(code.textContent);
|
107
|
+
btn.textContent = "Copied!";
|
108
|
+
setTimeout(() => { btn.textContent = "Copy"; }, 1200);
|
109
|
+
}
|
110
|
+
});
|
111
|
+
});
|
112
|
+
document.querySelectorAll(".view-raw-btn").forEach(btn => {
|
113
|
+
btn.addEventListener("click", e => {
|
114
|
+
e.preventDefault();
|
115
|
+
const codeId = btn.dataset.target;
|
116
|
+
const code = document.getElementById(codeId);
|
117
|
+
if (code) {
|
118
|
+
const win = window.open("", "_blank");
|
119
|
+
win.document.write("<pre>" + code.textContent.replace(/[<>&]/g, c => ({
|
120
|
+
'<': '<', '>': '>', '&': '&'
|
121
|
+
}[c])) + "</pre>");
|
122
|
+
win.document.close();
|
123
|
+
}
|
124
|
+
});
|
125
|
+
});
|
94
126
|
});
|
@@ -0,0 +1,208 @@
|
|
1
|
+
import os
|
2
|
+
import shutil
|
3
|
+
import uuid
|
4
|
+
import pandas as pd
|
5
|
+
import plotly.graph_objects as go
|
6
|
+
from dominate import document
|
7
|
+
from dominate.tags import div, h1, h2, h3, h4, p, a, script, link
|
8
|
+
from dominate.util import raw as raw_util
|
9
|
+
import html
|
10
|
+
|
11
|
+
class AbstractPage:
|
12
|
+
def __init__(self):
|
13
|
+
self.elements = []
|
14
|
+
|
15
|
+
def add_header(self, text, level=1):
|
16
|
+
if level not in (1, 2, 3, 4):
|
17
|
+
raise ValueError("Header level must be 1, 2, 3, or 4")
|
18
|
+
self.elements.append(("header", (text, level)))
|
19
|
+
|
20
|
+
def add_text(self, text):
|
21
|
+
self.elements.append(("text", text))
|
22
|
+
|
23
|
+
def add_plot(self, plot):
|
24
|
+
html_plot = raw_util(plot.to_html(full_html=False, include_plotlyjs='cdn', config={'responsive': True}))
|
25
|
+
self.elements.append(("plot", html_plot))
|
26
|
+
|
27
|
+
def add_table(self, df, table_id=None, sortable=True):
|
28
|
+
if table_id is None:
|
29
|
+
table_id = f"table-{len(self.elements)}"
|
30
|
+
classes = "table-hover table-striped"
|
31
|
+
if sortable:
|
32
|
+
classes += " sortable"
|
33
|
+
html_table = df.to_html(classes=classes, index=False, border=0, table_id=table_id, escape=False)
|
34
|
+
self.elements.append(("table", (html_table, table_id)))
|
35
|
+
|
36
|
+
def add_download(self, file_path, label=None):
|
37
|
+
if not os.path.isfile(file_path):
|
38
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
39
|
+
self.elements.append(("download", (file_path, label)))
|
40
|
+
|
41
|
+
def add_minipage(self, minipage):
|
42
|
+
self.elements.append(("minipage", minipage))
|
43
|
+
|
44
|
+
def add_syntax(self, code, language="python"):
|
45
|
+
self.elements.append(("syntax", (code, language)))
|
46
|
+
|
47
|
+
class Page(AbstractPage):
|
48
|
+
def __init__(self, slug, title):
|
49
|
+
super().__init__()
|
50
|
+
self.slug = slug
|
51
|
+
self.title = title
|
52
|
+
self.add_header(title, level=1)
|
53
|
+
|
54
|
+
def render(self, index, downloads_dir=None, relative_prefix=""):
|
55
|
+
elements = []
|
56
|
+
for kind, content in self.elements:
|
57
|
+
if kind == "minipage":
|
58
|
+
row_div = div(cls="minipage-row")
|
59
|
+
row_div += content.render(index, downloads_dir=downloads_dir, relative_prefix=relative_prefix)
|
60
|
+
elements.append(row_div)
|
61
|
+
elif kind == "header":
|
62
|
+
text, level = content
|
63
|
+
elements.append({1: h1, 2: h2, 3: h3, 4: h4}[level](text))
|
64
|
+
elif kind == "text":
|
65
|
+
elements.append(p(content))
|
66
|
+
elif kind == "plot":
|
67
|
+
elements.append(div(content, cls="plot-container"))
|
68
|
+
elif kind == "table":
|
69
|
+
table_html, _ = content
|
70
|
+
wrapped = div(raw_util(table_html), cls="table-wrapper")
|
71
|
+
elements.append(wrapped)
|
72
|
+
elif kind == "download":
|
73
|
+
file_path, label = content
|
74
|
+
file_uuid = f"{uuid.uuid4().hex}_{os.path.basename(file_path)}"
|
75
|
+
dst_path = os.path.join(downloads_dir, file_uuid)
|
76
|
+
shutil.copy2(file_path, dst_path)
|
77
|
+
btn = a(label or os.path.basename(file_path),
|
78
|
+
href=f"{relative_prefix}downloads/{file_uuid}",
|
79
|
+
cls="download-button",
|
80
|
+
download=True)
|
81
|
+
elements.append(div(btn))
|
82
|
+
elif kind == "syntax":
|
83
|
+
code, language = content
|
84
|
+
code_id = f"code-{uuid.uuid4().hex[:8]}"
|
85
|
+
toolbar = div(cls="code-toolbar")
|
86
|
+
toolbar += a("Copy", href="#", cls="copy-btn", **{"data-target": code_id})
|
87
|
+
toolbar += a("View Raw", href="#", cls="view-raw-btn", **{"data-target": code_id, "style": "margin-left:10px;"})
|
88
|
+
escaped_code = html.escape(code)
|
89
|
+
block_wrapper = div(
|
90
|
+
div(
|
91
|
+
toolbar,
|
92
|
+
raw_util(f'<pre><code id="{code_id}" class="language-{language}">{escaped_code}</code></pre>'),
|
93
|
+
cls="syntax-block"
|
94
|
+
)
|
95
|
+
)
|
96
|
+
elements.append(block_wrapper)
|
97
|
+
return elements
|
98
|
+
|
99
|
+
class MiniPage(AbstractPage):
|
100
|
+
def __init__(self):
|
101
|
+
super().__init__()
|
102
|
+
|
103
|
+
def render(self, index=None, downloads_dir=None, relative_prefix=""):
|
104
|
+
row_div = div(cls="minipage-row")
|
105
|
+
for kind, content in self.elements:
|
106
|
+
cell = div(cls="minipage-cell")
|
107
|
+
if kind == "header":
|
108
|
+
text, level = content
|
109
|
+
cell += {1: h1, 2: h2, 3: h3, 4: h4}[level](text)
|
110
|
+
elif kind == "text":
|
111
|
+
cell += p(content)
|
112
|
+
elif kind == "plot":
|
113
|
+
cell += div(content, cls="plot-container")
|
114
|
+
elif kind == "table":
|
115
|
+
table_html, _ = content
|
116
|
+
cell += div(raw_util(table_html), cls="table-wrapper")
|
117
|
+
elif kind == "download":
|
118
|
+
file_path, label = content
|
119
|
+
file_uuid = f"{uuid.uuid4().hex}_{os.path.basename(file_path)}"
|
120
|
+
dst_path = os.path.join(downloads_dir, file_uuid)
|
121
|
+
shutil.copy2(file_path, dst_path)
|
122
|
+
btn = a(label or os.path.basename(file_path),
|
123
|
+
href=f"{relative_prefix}downloads/{file_uuid}",
|
124
|
+
cls="download-button",
|
125
|
+
download=True)
|
126
|
+
cell += div(btn)
|
127
|
+
elif kind == "syntax":
|
128
|
+
code, language = content
|
129
|
+
code_id = f"code-{uuid.uuid4().hex[:8]}"
|
130
|
+
toolbar = div(cls="code-toolbar")
|
131
|
+
toolbar += a("Copy", href="#", cls="copy-btn", **{"data-target": code_id})
|
132
|
+
toolbar += a("View Raw", href="#", cls="view-raw-btn", **{"data-target": code_id, "style": "margin-left:10px;"})
|
133
|
+
escaped_code = html.escape(code)
|
134
|
+
block_wrapper = div(
|
135
|
+
div(
|
136
|
+
toolbar,
|
137
|
+
raw_util(f'<pre><code id="{code_id}" class="language-{language}">{escaped_code}</code></pre>'),
|
138
|
+
cls="syntax-block"
|
139
|
+
)
|
140
|
+
)
|
141
|
+
cell += block_wrapper
|
142
|
+
row_div += cell
|
143
|
+
return row_div
|
144
|
+
|
145
|
+
class Dashboard:
|
146
|
+
def __init__(self, title="Dashboard"):
|
147
|
+
self.title = title
|
148
|
+
self.pages = []
|
149
|
+
|
150
|
+
def add_page(self, page: Page):
|
151
|
+
self.pages.append(page)
|
152
|
+
|
153
|
+
def publish(self, output_dir="output"):
|
154
|
+
output_dir = os.path.abspath(output_dir)
|
155
|
+
pages_dir = os.path.join(output_dir, "pages")
|
156
|
+
downloads_dir = os.path.join(output_dir, "downloads")
|
157
|
+
assets_src = os.path.join(os.path.dirname(__file__), "assets")
|
158
|
+
assets_dst = os.path.join(output_dir, "assets")
|
159
|
+
|
160
|
+
os.makedirs(pages_dir, exist_ok=True)
|
161
|
+
os.makedirs(downloads_dir, exist_ok=True)
|
162
|
+
shutil.copytree(assets_src, assets_dst, dirs_exist_ok=True)
|
163
|
+
|
164
|
+
# Per-page HTML
|
165
|
+
for page in self.pages:
|
166
|
+
doc = document(title=page.title)
|
167
|
+
with doc.head:
|
168
|
+
doc.head.add(link(rel="stylesheet", href="../assets/css/style.css"))
|
169
|
+
doc.head.add(script(type="text/javascript", src="../assets/js/script.js"))
|
170
|
+
doc.head.add(script(src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"))
|
171
|
+
doc.head.add(link(rel="stylesheet", href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css"))
|
172
|
+
doc.head.add(script(src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"))
|
173
|
+
doc.head.add(script(src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-python.min.js"))
|
174
|
+
doc.head.add(script(src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-javascript.min.js"))
|
175
|
+
with doc:
|
176
|
+
with div(cls="page-section", id=f"page-{page.slug}") as section:
|
177
|
+
for el in page.render(0, downloads_dir=downloads_dir, relative_prefix="../"):
|
178
|
+
section += el
|
179
|
+
with open(os.path.join(pages_dir, f"{page.slug}.html"), "w") as f:
|
180
|
+
f.write(str(doc))
|
181
|
+
|
182
|
+
# Main index.html
|
183
|
+
index_doc = document(title=self.title)
|
184
|
+
with index_doc.head:
|
185
|
+
index_doc.head.add(link(rel="stylesheet", href="assets/css/style.css"))
|
186
|
+
index_doc.head.add(script(type="text/javascript", src="assets/js/script.js"))
|
187
|
+
index_doc.head.add(script(src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"))
|
188
|
+
index_doc.head.add(link(rel="stylesheet", href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css"))
|
189
|
+
index_doc.head.add(script(src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"))
|
190
|
+
index_doc.head.add(script(src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-python.min.js"))
|
191
|
+
index_doc.head.add(script(src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-javascript.min.js"))
|
192
|
+
|
193
|
+
with index_doc:
|
194
|
+
with div(id="sidebar"):
|
195
|
+
h1(self.title)
|
196
|
+
for page in self.pages:
|
197
|
+
a(page.title, cls="nav-link", href="#", **{"data-target": f"page-{page.slug}"})
|
198
|
+
with div(id="sidebar-footer"):
|
199
|
+
a("Produced by staticdash", href="https://pypi.org/project/staticdash/", target="_blank")
|
200
|
+
|
201
|
+
with div(id="content"):
|
202
|
+
for idx, page in enumerate(self.pages):
|
203
|
+
with div(id=f"page-{page.slug}", cls="page-section", style="display:none;") as section:
|
204
|
+
for el in page.render(idx, downloads_dir=downloads_dir, relative_prefix=""):
|
205
|
+
section += el
|
206
|
+
|
207
|
+
with open(os.path.join(output_dir, "index.html"), "w") as f:
|
208
|
+
f.write(str(index_doc))
|
@@ -1,205 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import shutil
|
3
|
-
import uuid
|
4
|
-
import pandas as pd
|
5
|
-
import plotly.graph_objects as go
|
6
|
-
from dominate import document
|
7
|
-
from dominate.tags import div, h1, h2, h3, h4, p, a, script, link
|
8
|
-
from dominate.util import raw as raw_util
|
9
|
-
|
10
|
-
class AbstractPage:
|
11
|
-
def __init__(self):
|
12
|
-
self.elements = []
|
13
|
-
|
14
|
-
def add_header(self, text, level=1):
|
15
|
-
if level not in (1, 2, 3, 4):
|
16
|
-
raise ValueError("Header level must be 1, 2, 3, or 4")
|
17
|
-
self.elements.append(("header", (text, level)))
|
18
|
-
|
19
|
-
def add_text(self, text):
|
20
|
-
self.elements.append(("text", text))
|
21
|
-
|
22
|
-
def add_plot(self, plot):
|
23
|
-
html = raw_util(plot.to_html(full_html=False, include_plotlyjs='cdn', config={'responsive': True}))
|
24
|
-
self.elements.append(("plot", html))
|
25
|
-
|
26
|
-
def add_table(self, df, table_id=None, sortable=True):
|
27
|
-
if table_id is None:
|
28
|
-
table_id = f"table-{len(self.elements)}"
|
29
|
-
classes = "table-hover table-striped"
|
30
|
-
if sortable:
|
31
|
-
classes += " sortable"
|
32
|
-
html = df.to_html(classes=classes, index=False, border=0, table_id=table_id, escape=False)
|
33
|
-
self.elements.append(("table", (html, table_id)))
|
34
|
-
|
35
|
-
def add_download(self, file_path, label=None):
|
36
|
-
if not os.path.isfile(file_path):
|
37
|
-
raise FileNotFoundError(f"File not found: {file_path}")
|
38
|
-
self.elements.append(("download", (file_path, label)))
|
39
|
-
|
40
|
-
def add_minipage(self, minipage):
|
41
|
-
self.elements.append(("minipage", minipage))
|
42
|
-
|
43
|
-
class Page(AbstractPage):
|
44
|
-
def __init__(self, slug, title):
|
45
|
-
super().__init__()
|
46
|
-
self.slug = slug
|
47
|
-
self.title = title
|
48
|
-
self.add_header(title, level=1)
|
49
|
-
|
50
|
-
def render(self, index):
|
51
|
-
section = div()
|
52
|
-
minipage_row = []
|
53
|
-
for kind, content in self.elements:
|
54
|
-
if kind == "minipage":
|
55
|
-
row_div = div(cls="minipage-row")
|
56
|
-
row_div += content.render(index)
|
57
|
-
section += row_div
|
58
|
-
elif kind == "header":
|
59
|
-
text, level = content
|
60
|
-
if level == 1:
|
61
|
-
section += h1(text)
|
62
|
-
elif level == 2:
|
63
|
-
section += h2(text)
|
64
|
-
elif level == 3:
|
65
|
-
section += h3(text)
|
66
|
-
elif level == 4:
|
67
|
-
section += h4(text)
|
68
|
-
elif kind == "text":
|
69
|
-
section += p(content)
|
70
|
-
elif kind == "plot":
|
71
|
-
section += div(content, cls="plot-container")
|
72
|
-
elif kind == "table":
|
73
|
-
table_html, _ = content
|
74
|
-
section += raw_util(table_html)
|
75
|
-
elif kind == "download":
|
76
|
-
file_path, label = content
|
77
|
-
btn = a(label or os.path.basename(file_path),
|
78
|
-
href=file_path,
|
79
|
-
cls="download-button",
|
80
|
-
download=True)
|
81
|
-
section += div(btn)
|
82
|
-
return section
|
83
|
-
|
84
|
-
class MiniPage(AbstractPage):
|
85
|
-
def __init__(self, width=1.0):
|
86
|
-
super().__init__()
|
87
|
-
self.width = width
|
88
|
-
|
89
|
-
def render(self, index=None):
|
90
|
-
style = f"flex: 0 0 {self.width * 100}%; max-width: {self.width * 100}%;"
|
91
|
-
container = div(cls="minipage", style=style)
|
92
|
-
minipage_row = []
|
93
|
-
for kind, content in self.elements:
|
94
|
-
if kind == "minipage":
|
95
|
-
minipage_row.append(content)
|
96
|
-
else:
|
97
|
-
if minipage_row:
|
98
|
-
row_div = div(cls="minipage-row")
|
99
|
-
for mp in minipage_row:
|
100
|
-
row_div += mp.render(index)
|
101
|
-
container += row_div
|
102
|
-
minipage_row = []
|
103
|
-
if kind == "header":
|
104
|
-
text, level = content
|
105
|
-
if level == 1:
|
106
|
-
container += h1(text)
|
107
|
-
elif level == 2:
|
108
|
-
container += h2(text)
|
109
|
-
elif level == 3:
|
110
|
-
container += h3(text)
|
111
|
-
elif level == 4:
|
112
|
-
container += h4(text)
|
113
|
-
elif kind == "text":
|
114
|
-
container += p(content)
|
115
|
-
elif kind == "plot":
|
116
|
-
container += div(content, cls="plot-container")
|
117
|
-
elif kind == "table":
|
118
|
-
table_html, _ = content
|
119
|
-
container += raw_util(table_html)
|
120
|
-
elif kind == "download":
|
121
|
-
file_path, label = content
|
122
|
-
btn = a(label or os.path.basename(file_path),
|
123
|
-
href=file_path,
|
124
|
-
cls="download-button",
|
125
|
-
download=True)
|
126
|
-
container += div(btn)
|
127
|
-
if minipage_row:
|
128
|
-
row_div = div(cls="minipage-row")
|
129
|
-
for mp in minipage_row:
|
130
|
-
row_div += mp.render(index)
|
131
|
-
container += row_div
|
132
|
-
return container
|
133
|
-
|
134
|
-
class Dashboard:
|
135
|
-
def __init__(self, title="Dashboard"):
|
136
|
-
self.title = title
|
137
|
-
self.pages = []
|
138
|
-
|
139
|
-
def add_page(self, page: Page):
|
140
|
-
self.pages.append(page)
|
141
|
-
|
142
|
-
def publish(self, output_dir="output"):
|
143
|
-
output_dir = os.path.abspath(output_dir)
|
144
|
-
pages_dir = os.path.join(output_dir, "pages")
|
145
|
-
downloads_dir = os.path.join(output_dir, "downloads")
|
146
|
-
assets_src = os.path.join(os.path.dirname(__file__), "assets")
|
147
|
-
assets_dst = os.path.join(output_dir, "assets")
|
148
|
-
|
149
|
-
os.makedirs(pages_dir, exist_ok=True)
|
150
|
-
os.makedirs(downloads_dir, exist_ok=True)
|
151
|
-
shutil.copytree(assets_src, assets_dst, dirs_exist_ok=True)
|
152
|
-
|
153
|
-
for page in self.pages:
|
154
|
-
doc = document(title=page.title)
|
155
|
-
with doc.head:
|
156
|
-
doc.head.add(link(rel="stylesheet", href="../assets/css/style.css"))
|
157
|
-
doc.head.add(script(type="text/javascript", src="../assets/js/script.js"))
|
158
|
-
doc.head.add(script(src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"))
|
159
|
-
|
160
|
-
with doc:
|
161
|
-
with div(cls="page-section", id=f"page-{page.slug}"):
|
162
|
-
for kind, content in page.elements:
|
163
|
-
if kind == "header":
|
164
|
-
text, level = content
|
165
|
-
if level == 1:
|
166
|
-
h1(text)
|
167
|
-
elif level == 2:
|
168
|
-
h2(text)
|
169
|
-
elif level == 3:
|
170
|
-
h3(text)
|
171
|
-
elif level == 4:
|
172
|
-
h4(text)
|
173
|
-
elif kind == "text":
|
174
|
-
p(content)
|
175
|
-
elif kind == "plot":
|
176
|
-
div(content, cls="plot-container")
|
177
|
-
elif kind == "table":
|
178
|
-
table_html, _ = content
|
179
|
-
doc.add(raw_util(table_html))
|
180
|
-
|
181
|
-
with open(os.path.join(pages_dir, f"{page.slug}.html"), "w") as f:
|
182
|
-
f.write(str(doc))
|
183
|
-
|
184
|
-
index_doc = document(title=self.title)
|
185
|
-
with index_doc.head:
|
186
|
-
index_doc.head.add(link(rel="stylesheet", href="assets/css/style.css"))
|
187
|
-
index_doc.head.add(script(type="text/javascript", src="assets/js/script.js"))
|
188
|
-
index_doc.head.add(script(src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"))
|
189
|
-
|
190
|
-
with index_doc:
|
191
|
-
with div(id="sidebar"):
|
192
|
-
h1(self.title)
|
193
|
-
for page in self.pages:
|
194
|
-
a(page.title, cls="nav-link", href="#", **{"data-target": f"page-{page.slug}"})
|
195
|
-
with div(id="sidebar-footer"):
|
196
|
-
a("Produced by staticdash", href="https://pypi.org/project/staticdash/", target="_blank")
|
197
|
-
|
198
|
-
with div(id="content"):
|
199
|
-
for idx, page in enumerate(self.pages):
|
200
|
-
with div(id=f"page-{page.slug}", cls="page-section", style="display:none;") as section:
|
201
|
-
rendered = page.render(idx)
|
202
|
-
section += rendered # This is correct
|
203
|
-
|
204
|
-
with open(os.path.join(output_dir, "index.html"), "w") as f:
|
205
|
-
f.write(str(index_doc))
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|