staticdash 2026.2__py3-none-any.whl → 2026.4__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/dashboard.py +283 -22
- {staticdash-2026.2.dist-info → staticdash-2026.4.dist-info}/METADATA +5 -3
- {staticdash-2026.2.dist-info → staticdash-2026.4.dist-info}/RECORD +5 -5
- {staticdash-2026.2.dist-info → staticdash-2026.4.dist-info}/WHEEL +0 -0
- {staticdash-2026.2.dist-info → staticdash-2026.4.dist-info}/top_level.txt +0 -0
staticdash/dashboard.py
CHANGED
|
@@ -10,8 +10,38 @@ from dominate.util import raw as raw_util
|
|
|
10
10
|
import html
|
|
11
11
|
import io
|
|
12
12
|
import base64
|
|
13
|
+
import matplotlib
|
|
13
14
|
from matplotlib import rc_context
|
|
14
15
|
|
|
16
|
+
def split_paragraphs_preserving_math(text):
|
|
17
|
+
"""
|
|
18
|
+
Split text into paragraphs on double newlines, preserving math expressions.
|
|
19
|
+
Assumes math is in $...$ (inline) or $$...$$ (display) format.
|
|
20
|
+
"""
|
|
21
|
+
math_blocks = []
|
|
22
|
+
|
|
23
|
+
# Replace math with placeholders
|
|
24
|
+
def replace_math(match):
|
|
25
|
+
math_blocks.append(match.group(0))
|
|
26
|
+
return f"__MATH_BLOCK_{len(math_blocks)-1}__"
|
|
27
|
+
|
|
28
|
+
# Handle display math first ($$...$$), then inline ($...$)
|
|
29
|
+
text = re.sub(r'\$\$([^$]+)\$\$', replace_math, text, flags=re.DOTALL)
|
|
30
|
+
text = re.sub(r'\$([^$]+)\$', replace_math, text, flags=re.DOTALL)
|
|
31
|
+
|
|
32
|
+
# Split on double newlines
|
|
33
|
+
paragraphs = text.split('\n\n')
|
|
34
|
+
|
|
35
|
+
# Restore math in each paragraph
|
|
36
|
+
restored_paragraphs = []
|
|
37
|
+
for para in paragraphs:
|
|
38
|
+
for i, block in enumerate(math_blocks):
|
|
39
|
+
para = para.replace(f"__MATH_BLOCK_{i}__", block)
|
|
40
|
+
restored_paragraphs.append(para.strip())
|
|
41
|
+
|
|
42
|
+
# Filter out empty paragraphs
|
|
43
|
+
return [para for para in restored_paragraphs if para]
|
|
44
|
+
|
|
15
45
|
class AbstractPage:
|
|
16
46
|
def __init__(self):
|
|
17
47
|
self.elements = []
|
|
@@ -24,8 +54,13 @@ class AbstractPage:
|
|
|
24
54
|
def add_text(self, text, width=None):
|
|
25
55
|
self.elements.append(("text", text, width))
|
|
26
56
|
|
|
27
|
-
def add_plot(self, plot, width=None):
|
|
28
|
-
|
|
57
|
+
def add_plot(self, plot, width=None, height=None, width_px=None, align="center"):
|
|
58
|
+
# Keep backward-compatible: `width` is a fraction (0..1) of page width.
|
|
59
|
+
# `height` is pixels (existing behavior). New optional `width_px`
|
|
60
|
+
# (pixels) can be supplied to control plot width. `align` controls
|
|
61
|
+
# horizontal alignment: 'left', 'center' (default), or 'right'.
|
|
62
|
+
# We store a tuple (plot, height_px, width_px, align) for forward-compatibility.
|
|
63
|
+
self.elements.append(("plot", (plot, height, width_px, align), width))
|
|
29
64
|
|
|
30
65
|
def add_table(self, df, table_id=None, sortable=True, width=None):
|
|
31
66
|
self.elements.append(("table", df, width))
|
|
@@ -111,13 +146,33 @@ class Page(AbstractPage):
|
|
|
111
146
|
outer_style = "display: flex; justify-content: center; margin: 0 auto;"
|
|
112
147
|
elem = None
|
|
113
148
|
if kind == "text":
|
|
114
|
-
|
|
149
|
+
paragraphs = split_paragraphs_preserving_math(content)
|
|
150
|
+
if paragraphs:
|
|
151
|
+
elem = div(*[p(para) for para in paragraphs])
|
|
152
|
+
else:
|
|
153
|
+
elem = p(content)
|
|
115
154
|
elif kind == "header":
|
|
116
155
|
text, level = content
|
|
117
156
|
header_tag = {1: h1, 2: h2, 3: h3, 4: h4}[level]
|
|
118
157
|
elem = header_tag(text)
|
|
119
158
|
elif kind == "plot":
|
|
120
|
-
|
|
159
|
+
# content may be stored as (figure, height), (figure, height, width_px)
|
|
160
|
+
# or (figure, height, width_px, align)
|
|
161
|
+
specified_height = None
|
|
162
|
+
specified_width_px = None
|
|
163
|
+
specified_align = "center"
|
|
164
|
+
if isinstance(content, (list, tuple)):
|
|
165
|
+
if len(content) == 4:
|
|
166
|
+
fig, specified_height, specified_width_px, specified_align = content
|
|
167
|
+
elif len(content) == 3:
|
|
168
|
+
fig, specified_height, specified_width_px = content
|
|
169
|
+
elif len(content) == 2:
|
|
170
|
+
fig, specified_height = content
|
|
171
|
+
else:
|
|
172
|
+
fig = content
|
|
173
|
+
else:
|
|
174
|
+
fig = content
|
|
175
|
+
|
|
121
176
|
if hasattr(fig, "to_html"):
|
|
122
177
|
# Use local Plotly loaded in <head>
|
|
123
178
|
# Ensure the figure uses a robust font family so minus signs and other
|
|
@@ -131,28 +186,128 @@ class Page(AbstractPage):
|
|
|
131
186
|
except Exception:
|
|
132
187
|
# Be defensive: don't fail rendering if layout manipulation isn't available
|
|
133
188
|
pass
|
|
134
|
-
|
|
135
|
-
# to avoid rendering issues when a user's font lacks the U+2212 glyph.
|
|
189
|
+
|
|
136
190
|
try:
|
|
137
|
-
|
|
138
|
-
|
|
191
|
+
# Temporarily set layout height/width if specified (pixels)
|
|
192
|
+
orig_height = None
|
|
193
|
+
orig_width = None
|
|
194
|
+
try:
|
|
195
|
+
orig_height = getattr(fig.layout, 'height', None)
|
|
196
|
+
except Exception:
|
|
197
|
+
orig_height = None
|
|
198
|
+
try:
|
|
199
|
+
orig_width = getattr(fig.layout, 'width', None)
|
|
200
|
+
except Exception:
|
|
201
|
+
orig_width = None
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
if specified_height is not None:
|
|
205
|
+
fig.update_layout(height=specified_height)
|
|
206
|
+
if specified_width_px is not None:
|
|
207
|
+
fig.update_layout(width=specified_width_px)
|
|
208
|
+
except Exception:
|
|
209
|
+
pass
|
|
210
|
+
|
|
211
|
+
# If a local vendored Plotly exists, rely on the head script.
|
|
212
|
+
# Otherwise include Plotly from CDN so the inline newPlot call works.
|
|
213
|
+
vendor_plotly = os.path.join(os.path.dirname(__file__), "assets", "vendor", "plotly", "plotly.min.js")
|
|
214
|
+
include_plotly = False
|
|
215
|
+
if not os.path.exists(vendor_plotly):
|
|
216
|
+
include_plotly = "cdn"
|
|
217
|
+
plotly_html = fig.to_html(full_html=False, include_plotlyjs=include_plotly, config={'responsive': True})
|
|
218
|
+
|
|
219
|
+
# Wrap the Plotly HTML in a container with explicit pixel sizing
|
|
220
|
+
container_style = "width:100%;"
|
|
221
|
+
if specified_width_px is not None:
|
|
222
|
+
container_style = f"width:{specified_width_px}px;"
|
|
223
|
+
if specified_height is not None:
|
|
224
|
+
container_style = container_style + f" height:{specified_height}px;"
|
|
225
|
+
|
|
226
|
+
plot_wrapped = f'<div style="{container_style}">{plotly_html}</div>'
|
|
227
|
+
# Apply alignment wrapper
|
|
228
|
+
if specified_align not in ("left", "right", "center"):
|
|
229
|
+
specified_align = "center"
|
|
230
|
+
if specified_align == "center":
|
|
231
|
+
align_style = "display:flex; justify-content:center; align-items:center;"
|
|
232
|
+
elif specified_align == "left":
|
|
233
|
+
align_style = "display:flex; justify-content:flex-start; align-items:center;"
|
|
234
|
+
else:
|
|
235
|
+
align_style = "display:flex; justify-content:flex-end; align-items:center;"
|
|
236
|
+
|
|
237
|
+
outer = f'<div style="{align_style}">{plot_wrapped}</div>'
|
|
238
|
+
elem = div(raw_util(outer))
|
|
239
|
+
|
|
240
|
+
# restore original height/width if we changed them
|
|
241
|
+
try:
|
|
242
|
+
if specified_height is not None:
|
|
243
|
+
fig.update_layout(height=orig_height)
|
|
244
|
+
if specified_width_px is not None:
|
|
245
|
+
fig.update_layout(width=orig_width)
|
|
246
|
+
except Exception:
|
|
247
|
+
pass
|
|
139
248
|
except Exception as e:
|
|
140
249
|
elem = div(f"Plotly figure could not be rendered: {e}")
|
|
141
250
|
else:
|
|
251
|
+
# Robust Matplotlib -> PNG path. Ensure `buf` exists and is closed.
|
|
252
|
+
buf = io.BytesIO()
|
|
142
253
|
try:
|
|
143
|
-
|
|
144
|
-
|
|
254
|
+
# If pixel width/height specified, attempt to adjust figure size
|
|
255
|
+
orig_size = None
|
|
256
|
+
try:
|
|
257
|
+
dpi = fig.get_dpi()
|
|
258
|
+
except Exception:
|
|
259
|
+
dpi = None
|
|
260
|
+
try:
|
|
261
|
+
if dpi is not None and (specified_height is not None or specified_width_px is not None):
|
|
262
|
+
orig_size = fig.get_size_inches()
|
|
263
|
+
new_w = orig_size[0]
|
|
264
|
+
new_h = orig_size[1]
|
|
265
|
+
if specified_width_px is not None:
|
|
266
|
+
new_w = specified_width_px / dpi
|
|
267
|
+
if specified_height is not None:
|
|
268
|
+
new_h = specified_height / dpi
|
|
269
|
+
fig.set_size_inches(new_w, new_h)
|
|
270
|
+
except Exception:
|
|
271
|
+
orig_size = None
|
|
272
|
+
|
|
145
273
|
with rc_context({"axes.unicode_minus": False}):
|
|
146
274
|
fig.savefig(buf, format="png", bbox_inches="tight")
|
|
275
|
+
|
|
276
|
+
# restore original size if changed
|
|
277
|
+
try:
|
|
278
|
+
if orig_size is not None:
|
|
279
|
+
fig.set_size_inches(orig_size)
|
|
280
|
+
except Exception:
|
|
281
|
+
pass
|
|
282
|
+
|
|
147
283
|
buf.seek(0)
|
|
148
284
|
img_base64 = base64.b64encode(buf.read()).decode("utf-8")
|
|
149
|
-
|
|
285
|
+
img_style = "max-width:100%;"
|
|
286
|
+
if specified_height is not None:
|
|
287
|
+
img_style = img_style + f" height:{specified_height}px;"
|
|
288
|
+
if specified_width_px is not None:
|
|
289
|
+
img_style = img_style + f" width:{specified_width_px}px;"
|
|
290
|
+
|
|
291
|
+
if specified_align not in ("left", "right", "center"):
|
|
292
|
+
specified_align = "center"
|
|
293
|
+
if specified_align == "center":
|
|
294
|
+
align_style = "display:flex; justify-content:center; align-items:center;"
|
|
295
|
+
elif specified_align == "left":
|
|
296
|
+
align_style = "display:flex; justify-content:flex-start; align-items:center;"
|
|
297
|
+
else:
|
|
298
|
+
align_style = "display:flex; justify-content:flex-end; align-items:center;"
|
|
299
|
+
|
|
150
300
|
elem = div(
|
|
151
|
-
raw_util(f'<img src="data:image/png;base64,{img_base64}" style="
|
|
152
|
-
style=
|
|
301
|
+
raw_util(f'<img src="data:image/png;base64,{img_base64}" style="{img_style}">'),
|
|
302
|
+
style=align_style
|
|
153
303
|
)
|
|
154
304
|
except Exception as e:
|
|
155
305
|
elem = div(f"Matplotlib figure could not be rendered: {e}")
|
|
306
|
+
finally:
|
|
307
|
+
try:
|
|
308
|
+
buf.close()
|
|
309
|
+
except Exception:
|
|
310
|
+
pass
|
|
156
311
|
elif kind == "table":
|
|
157
312
|
df = content
|
|
158
313
|
try:
|
|
@@ -213,13 +368,29 @@ class MiniPage(AbstractPage):
|
|
|
213
368
|
outer_style = "display: flex; justify-content: center; margin: 0 auto;"
|
|
214
369
|
elem = None
|
|
215
370
|
if kind == "text":
|
|
216
|
-
|
|
371
|
+
paragraphs = split_paragraphs_preserving_math(content)
|
|
372
|
+
if paragraphs:
|
|
373
|
+
elem = div(*[p(para) for para in paragraphs])
|
|
374
|
+
else:
|
|
375
|
+
elem = p(content)
|
|
217
376
|
elif kind == "header":
|
|
218
377
|
text, level = content
|
|
219
378
|
header_tag = {1: h1, 2: h2, 3: h3, 4: h4}[level]
|
|
220
379
|
elem = header_tag(text)
|
|
221
380
|
elif kind == "plot":
|
|
222
|
-
|
|
381
|
+
# content may be stored as (figure, height) or (figure, height, width_px)
|
|
382
|
+
specified_height = None
|
|
383
|
+
specified_width_px = None
|
|
384
|
+
if isinstance(content, (list, tuple)):
|
|
385
|
+
if len(content) == 3:
|
|
386
|
+
fig, specified_height, specified_width_px = content
|
|
387
|
+
elif len(content) == 2:
|
|
388
|
+
fig, specified_height = content
|
|
389
|
+
else:
|
|
390
|
+
fig = content
|
|
391
|
+
else:
|
|
392
|
+
fig = content
|
|
393
|
+
|
|
223
394
|
if hasattr(fig, "to_html"):
|
|
224
395
|
# Use local Plotly loaded in <head>
|
|
225
396
|
# Ensure the figure uses a robust font family so minus signs and other
|
|
@@ -233,25 +404,115 @@ class MiniPage(AbstractPage):
|
|
|
233
404
|
except Exception:
|
|
234
405
|
pass
|
|
235
406
|
try:
|
|
236
|
-
|
|
237
|
-
|
|
407
|
+
orig_height = None
|
|
408
|
+
orig_width = None
|
|
409
|
+
try:
|
|
410
|
+
orig_height = getattr(fig.layout, 'height', None)
|
|
411
|
+
except Exception:
|
|
412
|
+
orig_height = None
|
|
413
|
+
try:
|
|
414
|
+
orig_width = getattr(fig.layout, 'width', None)
|
|
415
|
+
except Exception:
|
|
416
|
+
orig_width = None
|
|
417
|
+
|
|
418
|
+
try:
|
|
419
|
+
if specified_height is not None:
|
|
420
|
+
fig.update_layout(height=specified_height)
|
|
421
|
+
if specified_width_px is not None:
|
|
422
|
+
fig.update_layout(width=specified_width_px)
|
|
423
|
+
except Exception:
|
|
424
|
+
pass
|
|
425
|
+
|
|
426
|
+
vendor_plotly = os.path.join(os.path.dirname(__file__), "assets", "vendor", "plotly", "plotly.min.js")
|
|
427
|
+
include_plotly = False
|
|
428
|
+
if not os.path.exists(vendor_plotly):
|
|
429
|
+
include_plotly = "cdn"
|
|
430
|
+
plotly_html = fig.to_html(full_html=False, include_plotlyjs=include_plotly, config={'responsive': True})
|
|
431
|
+
container_style = "width:100%;"
|
|
432
|
+
if specified_width_px is not None:
|
|
433
|
+
container_style = f"width:{specified_width_px}px;"
|
|
434
|
+
if specified_height is not None:
|
|
435
|
+
container_style = container_style + f" height:{specified_height}px;"
|
|
436
|
+
plot_wrapped = f'<div style="{container_style}">{plotly_html}</div>'
|
|
437
|
+
if specified_align not in ("left", "right", "center"):
|
|
438
|
+
specified_align = "center"
|
|
439
|
+
if specified_align == "center":
|
|
440
|
+
align_style = "display:flex; justify-content:center; align-items:center;"
|
|
441
|
+
elif specified_align == "left":
|
|
442
|
+
align_style = "display:flex; justify-content:flex-start; align-items:center;"
|
|
443
|
+
else:
|
|
444
|
+
align_style = "display:flex; justify-content:flex-end; align-items:center;"
|
|
445
|
+
outer = f'<div style="{align_style}">{plot_wrapped}</div>'
|
|
446
|
+
elem = div(raw_util(outer))
|
|
447
|
+
|
|
448
|
+
try:
|
|
449
|
+
if specified_height is not None:
|
|
450
|
+
fig.update_layout(height=orig_height)
|
|
451
|
+
if specified_width_px is not None:
|
|
452
|
+
fig.update_layout(width=orig_width)
|
|
453
|
+
except Exception:
|
|
454
|
+
pass
|
|
238
455
|
except Exception as e:
|
|
239
456
|
elem = div(f"Plotly figure could not be rendered: {e}")
|
|
240
457
|
else:
|
|
458
|
+
# Robust Matplotlib -> PNG path. Ensure `buf` exists and is closed.
|
|
459
|
+
buf = io.BytesIO()
|
|
241
460
|
try:
|
|
242
|
-
|
|
243
|
-
|
|
461
|
+
# If pixel width/height specified, attempt to adjust figure size
|
|
462
|
+
orig_size = None
|
|
463
|
+
try:
|
|
464
|
+
dpi = fig.get_dpi()
|
|
465
|
+
except Exception:
|
|
466
|
+
dpi = None
|
|
467
|
+
try:
|
|
468
|
+
if dpi is not None and (specified_height is not None or specified_width_px is not None):
|
|
469
|
+
orig_size = fig.get_size_inches()
|
|
470
|
+
new_w = orig_size[0]
|
|
471
|
+
new_h = orig_size[1]
|
|
472
|
+
if specified_width_px is not None:
|
|
473
|
+
new_w = specified_width_px / dpi
|
|
474
|
+
if specified_height is not None:
|
|
475
|
+
new_h = specified_height / dpi
|
|
476
|
+
fig.set_size_inches(new_w, new_h)
|
|
477
|
+
except Exception:
|
|
478
|
+
orig_size = None
|
|
479
|
+
|
|
244
480
|
with rc_context({"axes.unicode_minus": False}):
|
|
245
481
|
fig.savefig(buf, format="png", bbox_inches="tight")
|
|
482
|
+
|
|
483
|
+
# restore original size if changed
|
|
484
|
+
try:
|
|
485
|
+
if orig_size is not None:
|
|
486
|
+
fig.set_size_inches(orig_size)
|
|
487
|
+
except Exception:
|
|
488
|
+
pass
|
|
489
|
+
|
|
246
490
|
buf.seek(0)
|
|
247
491
|
img_base64 = base64.b64encode(buf.read()).decode("utf-8")
|
|
248
|
-
|
|
492
|
+
img_style = "max-width:100%;"
|
|
493
|
+
if specified_height is not None:
|
|
494
|
+
img_style = img_style + f" height:{specified_height}px;"
|
|
495
|
+
if specified_width_px is not None:
|
|
496
|
+
img_style = img_style + f" width:{specified_width_px}px;"
|
|
497
|
+
if specified_align not in ("left", "right", "center"):
|
|
498
|
+
specified_align = "center"
|
|
499
|
+
if specified_align == "center":
|
|
500
|
+
align_style = "display:flex; justify-content:center; align-items:center;"
|
|
501
|
+
elif specified_align == "left":
|
|
502
|
+
align_style = "display:flex; justify-content:flex-start; align-items:center;"
|
|
503
|
+
else:
|
|
504
|
+
align_style = "display:flex; justify-content:flex-end; align-items:center;"
|
|
249
505
|
elem = div(
|
|
250
|
-
raw_util(f'<img src="data:image/png;base64,{img_base64}" style="
|
|
251
|
-
style=
|
|
506
|
+
raw_util(f'<img src="data:image/png;base64,{img_base64}" style="{img_style}">'),
|
|
507
|
+
style=align_style
|
|
252
508
|
)
|
|
253
509
|
except Exception as e:
|
|
254
510
|
elem = div(f"Matplotlib figure could not be rendered: {e}")
|
|
511
|
+
finally:
|
|
512
|
+
try:
|
|
513
|
+
buf.close()
|
|
514
|
+
except Exception:
|
|
515
|
+
pass
|
|
255
516
|
elif kind == "table":
|
|
256
517
|
df = content
|
|
257
518
|
html_table = df.to_html(classes="table-hover table-striped", index=False, border=0, table_id=f"table-{index}", escape=False)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: staticdash
|
|
3
|
-
Version: 2026.
|
|
3
|
+
Version: 2026.4
|
|
4
4
|
Summary: A lightweight static HTML dashboard generator with Plotly and pandas support.
|
|
5
5
|
Author-email: Brian Day <brian.day1@gmail.com>
|
|
6
6
|
License: CC0-1.0
|
|
@@ -11,9 +11,11 @@ Description-Content-Type: text/markdown
|
|
|
11
11
|
Requires-Dist: plotly
|
|
12
12
|
Requires-Dist: pandas
|
|
13
13
|
Requires-Dist: dominate
|
|
14
|
-
Requires-Dist: reportlab
|
|
15
|
-
Requires-Dist: kaleido
|
|
16
14
|
Requires-Dist: matplotlib
|
|
15
|
+
Provides-Extra: images
|
|
16
|
+
Requires-Dist: kaleido; extra == "images"
|
|
17
|
+
Provides-Extra: all
|
|
18
|
+
Requires-Dist: kaleido; extra == "all"
|
|
17
19
|
|
|
18
20
|
# staticdash
|
|
19
21
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
staticdash/__init__.py,sha256=MQGR6LAqx2aFEA64MZz1ADxwpXLPn3VYNVIyjt9qx4Q,268
|
|
2
|
-
staticdash/dashboard.py,sha256=
|
|
2
|
+
staticdash/dashboard.py,sha256=pR1AmmisyBAiPTUHlijPwF2E6bQJjU-9ZhWGTcVSeC0,42833
|
|
3
3
|
staticdash/assets/css/style.css,sha256=AOYdkw-nK_WvV6im_Y34gz4rJZWifk5o-mRmCKwMP60,7014
|
|
4
4
|
staticdash/assets/js/script.js,sha256=7xBRlz_19wybbNVwAcfuKNXtDEojGB4EB0Yj4klsoTA,6998
|
|
5
5
|
staticdash/assets/vendor/mathjax/tex-mml-chtml.js,sha256=MASABpB4tYktI2Oitl4t-78w_lyA-D7b_s9GEP0JOGI,1173007
|
|
@@ -13,7 +13,7 @@ staticdash/assets/vendor/prism/components/prism-json.min.js,sha256=lW2GuqWufsQQZ
|
|
|
13
13
|
staticdash/assets/vendor/prism/components/prism-markup.min.js,sha256=h5_J0lbDUtmA4FOFf6cHMwhTuL-2fOKE6mYaJN7FdW4,2850
|
|
14
14
|
staticdash/assets/vendor/prism/components/prism-python.min.js,sha256=7UOFaFvPLUk1yNu6tL3hZgPaEyngktK_NsPa3WfpqFw,2113
|
|
15
15
|
staticdash/assets/vendor/prism/components/prism-sql.min.js,sha256=P8X4zmmVDsc63JcvBh30Kq6nj6pIZHCRNOoq3Ag_OjM,3261
|
|
16
|
-
staticdash-2026.
|
|
17
|
-
staticdash-2026.
|
|
18
|
-
staticdash-2026.
|
|
19
|
-
staticdash-2026.
|
|
16
|
+
staticdash-2026.4.dist-info/METADATA,sha256=FhGqUb1OWQuzZ_Jjtip3xV5gQjzJRHBV2pDuOTe2Y6A,2521
|
|
17
|
+
staticdash-2026.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
18
|
+
staticdash-2026.4.dist-info/top_level.txt,sha256=3MzZU6SptkUkjcHV1cvPji0H4aRzPphLHnpStgGEcxM,11
|
|
19
|
+
staticdash-2026.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|