staticdash 0.3.1__py3-none-any.whl → 0.3.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.
- staticdash/assets/css/style.css +80 -1
- staticdash/assets/js/script.js +35 -3
- staticdash/dashboard.py +98 -91
- {staticdash-0.3.1.dist-info → staticdash-0.3.2.dist-info}/METADATA +1 -1
- staticdash-0.3.2.dist-info/RECORD +8 -0
- staticdash-0.3.1.dist-info/RECORD +0 -8
- {staticdash-0.3.1.dist-info → staticdash-0.3.2.dist-info}/WHEEL +0 -0
- {staticdash-0.3.1.dist-info → staticdash-0.3.2.dist-info}/top_level.txt +0 -0
staticdash/assets/css/style.css
CHANGED
@@ -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
|
}
|
staticdash/assets/js/script.js
CHANGED
@@ -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
|
});
|
staticdash/dashboard.py
CHANGED
@@ -6,6 +6,7 @@ import plotly.graph_objects as go
|
|
6
6
|
from dominate import document
|
7
7
|
from dominate.tags import div, h1, h2, h3, h4, p, a, script, link
|
8
8
|
from dominate.util import raw as raw_util
|
9
|
+
import html
|
9
10
|
|
10
11
|
class AbstractPage:
|
11
12
|
def __init__(self):
|
@@ -20,8 +21,8 @@ class AbstractPage:
|
|
20
21
|
self.elements.append(("text", text))
|
21
22
|
|
22
23
|
def add_plot(self, plot):
|
23
|
-
|
24
|
-
self.elements.append(("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))
|
25
26
|
|
26
27
|
def add_table(self, df, table_id=None, sortable=True):
|
27
28
|
if table_id is None:
|
@@ -29,8 +30,8 @@ class AbstractPage:
|
|
29
30
|
classes = "table-hover table-striped"
|
30
31
|
if sortable:
|
31
32
|
classes += " sortable"
|
32
|
-
|
33
|
-
self.elements.append(("table", (
|
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)))
|
34
35
|
|
35
36
|
def add_download(self, file_path, label=None):
|
36
37
|
if not os.path.isfile(file_path):
|
@@ -40,6 +41,9 @@ class AbstractPage:
|
|
40
41
|
def add_minipage(self, minipage):
|
41
42
|
self.elements.append(("minipage", minipage))
|
42
43
|
|
44
|
+
def add_syntax(self, code, language="python"):
|
45
|
+
self.elements.append(("syntax", (code, language)))
|
46
|
+
|
43
47
|
class Page(AbstractPage):
|
44
48
|
def __init__(self, slug, title):
|
45
49
|
super().__init__()
|
@@ -47,89 +51,100 @@ class Page(AbstractPage):
|
|
47
51
|
self.title = title
|
48
52
|
self.add_header(title, level=1)
|
49
53
|
|
50
|
-
def render(self, index):
|
51
|
-
|
52
|
-
minipage_row = []
|
54
|
+
def render(self, index, downloads_dir=None, relative_prefix=""):
|
55
|
+
elements = []
|
53
56
|
for kind, content in self.elements:
|
54
57
|
if kind == "minipage":
|
55
58
|
row_div = div(cls="minipage-row")
|
56
|
-
row_div += content.render(index)
|
57
|
-
|
59
|
+
row_div += content.render(index, downloads_dir=downloads_dir, relative_prefix=relative_prefix)
|
60
|
+
elements.append(row_div)
|
58
61
|
elif kind == "header":
|
59
62
|
text, level = content
|
60
|
-
|
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)
|
63
|
+
elements.append({1: h1, 2: h2, 3: h3, 4: h4}[level](text))
|
68
64
|
elif kind == "text":
|
69
|
-
|
65
|
+
elements.append(p(content))
|
70
66
|
elif kind == "plot":
|
71
|
-
|
67
|
+
elements.append(div(content, cls="plot-container"))
|
72
68
|
elif kind == "table":
|
73
69
|
table_html, _ = content
|
74
|
-
|
70
|
+
wrapped = div(raw_util(table_html), cls="table-wrapper")
|
71
|
+
elements.append(wrapped)
|
75
72
|
elif kind == "download":
|
76
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
77
|
btn = a(label or os.path.basename(file_path),
|
78
|
-
href=
|
78
|
+
href=f"{relative_prefix}downloads/{file_uuid}",
|
79
79
|
cls="download-button",
|
80
80
|
download=True)
|
81
|
-
|
82
|
-
|
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
|
+
header_text = f"{language.capitalize()} Example"
|
90
|
+
block_wrapper = div(
|
91
|
+
h4(header_text),
|
92
|
+
div(
|
93
|
+
toolbar,
|
94
|
+
raw_util(f'<pre><code id="{code_id}" class="language-{language}">{escaped_code}</code></pre>'),
|
95
|
+
cls="syntax-block"
|
96
|
+
)
|
97
|
+
)
|
98
|
+
elements.append(block_wrapper)
|
99
|
+
return elements
|
83
100
|
|
84
101
|
class MiniPage(AbstractPage):
|
85
|
-
def __init__(self
|
102
|
+
def __init__(self):
|
86
103
|
super().__init__()
|
87
|
-
self.width = width
|
88
104
|
|
89
|
-
def render(self, index=None):
|
90
|
-
|
91
|
-
container = div(cls="minipage", style=style)
|
92
|
-
minipage_row = []
|
105
|
+
def render(self, index=None, downloads_dir=None, relative_prefix=""):
|
106
|
+
row_div = div(cls="minipage-row")
|
93
107
|
for kind, content in self.elements:
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
108
|
+
cell = div(cls="minipage-cell")
|
109
|
+
if kind == "header":
|
110
|
+
text, level = content
|
111
|
+
cell += {1: h1, 2: h2, 3: h3, 4: h4}[level](text)
|
112
|
+
elif kind == "text":
|
113
|
+
cell += p(content)
|
114
|
+
elif kind == "plot":
|
115
|
+
cell += div(content, cls="plot-container")
|
116
|
+
elif kind == "table":
|
117
|
+
table_html, _ = content
|
118
|
+
cell += div(raw_util(table_html), cls="table-wrapper")
|
119
|
+
elif kind == "download":
|
120
|
+
file_path, label = content
|
121
|
+
file_uuid = f"{uuid.uuid4().hex}_{os.path.basename(file_path)}"
|
122
|
+
dst_path = os.path.join(downloads_dir, file_uuid)
|
123
|
+
shutil.copy2(file_path, dst_path)
|
124
|
+
btn = a(label or os.path.basename(file_path),
|
125
|
+
href=f"{relative_prefix}downloads/{file_uuid}",
|
126
|
+
cls="download-button",
|
127
|
+
download=True)
|
128
|
+
cell += div(btn)
|
129
|
+
elif kind == "syntax":
|
130
|
+
code, language = content
|
131
|
+
code_id = f"code-{uuid.uuid4().hex[:8]}"
|
132
|
+
toolbar = div(cls="code-toolbar")
|
133
|
+
toolbar += a("Copy", href="#", cls="copy-btn", **{"data-target": code_id})
|
134
|
+
toolbar += a("View Raw", href="#", cls="view-raw-btn", **{"data-target": code_id, "style": "margin-left:10px;"})
|
135
|
+
escaped_code = html.escape(code)
|
136
|
+
header_text = f"{language.capitalize()} Example"
|
137
|
+
block_wrapper = div(
|
138
|
+
h4(header_text),
|
139
|
+
div(
|
140
|
+
toolbar,
|
141
|
+
raw_util(f'<pre><code id="{code_id}" class="language-{language}">{escaped_code}</code></pre>'),
|
142
|
+
cls="syntax-block"
|
143
|
+
)
|
144
|
+
)
|
145
|
+
cell += block_wrapper
|
146
|
+
row_div += cell
|
147
|
+
return row_div
|
133
148
|
|
134
149
|
class Dashboard:
|
135
150
|
def __init__(self, title="Dashboard"):
|
@@ -150,42 +165,34 @@ class Dashboard:
|
|
150
165
|
os.makedirs(downloads_dir, exist_ok=True)
|
151
166
|
shutil.copytree(assets_src, assets_dst, dirs_exist_ok=True)
|
152
167
|
|
168
|
+
# Per-page HTML
|
153
169
|
for page in self.pages:
|
154
170
|
doc = document(title=page.title)
|
155
171
|
with doc.head:
|
156
172
|
doc.head.add(link(rel="stylesheet", href="../assets/css/style.css"))
|
157
173
|
doc.head.add(script(type="text/javascript", src="../assets/js/script.js"))
|
158
174
|
doc.head.add(script(src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"))
|
159
|
-
|
175
|
+
doc.head.add(link(rel="stylesheet", href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css"))
|
176
|
+
doc.head.add(script(src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"))
|
177
|
+
doc.head.add(script(src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-python.min.js"))
|
178
|
+
doc.head.add(script(src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-javascript.min.js"))
|
160
179
|
with doc:
|
161
|
-
with div(cls="page-section", id=f"page-{page.slug}"):
|
162
|
-
for
|
163
|
-
|
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
|
-
|
180
|
+
with div(cls="page-section", id=f"page-{page.slug}") as section:
|
181
|
+
for el in page.render(0, downloads_dir=downloads_dir, relative_prefix="../"):
|
182
|
+
section += el
|
181
183
|
with open(os.path.join(pages_dir, f"{page.slug}.html"), "w") as f:
|
182
184
|
f.write(str(doc))
|
183
185
|
|
186
|
+
# Main index.html
|
184
187
|
index_doc = document(title=self.title)
|
185
188
|
with index_doc.head:
|
186
189
|
index_doc.head.add(link(rel="stylesheet", href="assets/css/style.css"))
|
187
190
|
index_doc.head.add(script(type="text/javascript", src="assets/js/script.js"))
|
188
191
|
index_doc.head.add(script(src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"))
|
192
|
+
index_doc.head.add(link(rel="stylesheet", href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css"))
|
193
|
+
index_doc.head.add(script(src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"))
|
194
|
+
index_doc.head.add(script(src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-python.min.js"))
|
195
|
+
index_doc.head.add(script(src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-javascript.min.js"))
|
189
196
|
|
190
197
|
with index_doc:
|
191
198
|
with div(id="sidebar"):
|
@@ -198,8 +205,8 @@ class Dashboard:
|
|
198
205
|
with div(id="content"):
|
199
206
|
for idx, page in enumerate(self.pages):
|
200
207
|
with div(id=f"page-{page.slug}", cls="page-section", style="display:none;") as section:
|
201
|
-
|
202
|
-
|
208
|
+
for el in page.render(idx, downloads_dir=downloads_dir, relative_prefix=""):
|
209
|
+
section += el
|
203
210
|
|
204
211
|
with open(os.path.join(output_dir, "index.html"), "w") as f:
|
205
212
|
f.write(str(index_doc))
|
@@ -0,0 +1,8 @@
|
|
1
|
+
staticdash/__init__.py,sha256=KqViaDkiQnhBI8-j3hr14umLDmPgddvdB_G1nJeC5Xs,38
|
2
|
+
staticdash/dashboard.py,sha256=_ZF-4HqqKQLDaKSNtizNbru0qDzDAT2xhTtzR8h-Rr0,9985
|
3
|
+
staticdash/assets/css/style.css,sha256=I0IAJoAxyPjx5DHD4f_D0xKgFGtZOpnOUlD2qomCVfw,3978
|
4
|
+
staticdash/assets/js/script.js,sha256=pXqvTyw1LRNYl5u1kQbRtQkwe0dnd1yDs1Jstj7S-GU,4148
|
5
|
+
staticdash-0.3.2.dist-info/METADATA,sha256=lt-Q8ce2Azr3bqY8YXaGy3-z_EXQuPP3aKR7Kup8x20,1776
|
6
|
+
staticdash-0.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
7
|
+
staticdash-0.3.2.dist-info/top_level.txt,sha256=3MzZU6SptkUkjcHV1cvPji0H4aRzPphLHnpStgGEcxM,11
|
8
|
+
staticdash-0.3.2.dist-info/RECORD,,
|
@@ -1,8 +0,0 @@
|
|
1
|
-
staticdash/__init__.py,sha256=KqViaDkiQnhBI8-j3hr14umLDmPgddvdB_G1nJeC5Xs,38
|
2
|
-
staticdash/dashboard.py,sha256=OwQtURb0E5DNm4lyvAzy6WlAqfcFoXsmSIVDwrzuc2U,8212
|
3
|
-
staticdash/assets/css/style.css,sha256=z3bC-QjAb9f5E3HUkr6R81qQyAfKpMW48yjy11CYH-Q,2779
|
4
|
-
staticdash/assets/js/script.js,sha256=SMOyh7_E_BlvLYMEwYlvCnV7-GnMR-x8PtEiFbIIAsw,3089
|
5
|
-
staticdash-0.3.1.dist-info/METADATA,sha256=kXmv9eqBZU0MuQMPfAQGKkWZEaqzxtqRBQMig_3tysk,1776
|
6
|
-
staticdash-0.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
7
|
-
staticdash-0.3.1.dist-info/top_level.txt,sha256=3MzZU6SptkUkjcHV1cvPji0H4aRzPphLHnpStgGEcxM,11
|
8
|
-
staticdash-0.3.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|