staticdash 2025.32__tar.gz → 2025.34__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.
Files changed (25) hide show
  1. {staticdash-2025.32 → staticdash-2025.34}/PKG-INFO +1 -1
  2. {staticdash-2025.32 → staticdash-2025.34}/pyproject.toml +1 -1
  3. {staticdash-2025.32 → staticdash-2025.34}/staticdash/__init__.py +2 -2
  4. {staticdash-2025.32 → staticdash-2025.34}/staticdash/dashboard.py +238 -5
  5. {staticdash-2025.32 → staticdash-2025.34}/staticdash.egg-info/PKG-INFO +1 -1
  6. {staticdash-2025.32 → staticdash-2025.34}/README.md +0 -0
  7. {staticdash-2025.32 → staticdash-2025.34}/setup.cfg +0 -0
  8. {staticdash-2025.32 → staticdash-2025.34}/setup.py +0 -0
  9. {staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/css/style.css +0 -0
  10. {staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/js/script.js +0 -0
  11. {staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/vendor/mathjax/tex-mml-chtml.js +0 -0
  12. {staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/vendor/plotly/plotly.min.js +0 -0
  13. {staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/vendor/prism/components/prism-bash.min.js +0 -0
  14. {staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/vendor/prism/components/prism-c.min.js +0 -0
  15. {staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/vendor/prism/components/prism-javascript.min.js +0 -0
  16. {staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/vendor/prism/components/prism-json.min.js +0 -0
  17. {staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/vendor/prism/components/prism-markup.min.js +0 -0
  18. {staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/vendor/prism/components/prism-python.min.js +0 -0
  19. {staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/vendor/prism/components/prism-sql.min.js +0 -0
  20. {staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/vendor/prism/prism-tomorrow.min.css +0 -0
  21. {staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/vendor/prism/prism.min.js +0 -0
  22. {staticdash-2025.32 → staticdash-2025.34}/staticdash.egg-info/SOURCES.txt +0 -0
  23. {staticdash-2025.32 → staticdash-2025.34}/staticdash.egg-info/dependency_links.txt +0 -0
  24. {staticdash-2025.32 → staticdash-2025.34}/staticdash.egg-info/requires.txt +0 -0
  25. {staticdash-2025.32 → staticdash-2025.34}/staticdash.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: staticdash
3
- Version: 2025.32
3
+ Version: 2025.34
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "staticdash"
7
- version = "2025.32"
7
+ version = "2025.34"
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" }
@@ -1,5 +1,5 @@
1
1
  # Import everything you want to expose at the package level
2
- from .dashboard import Dashboard, MiniPage, Page
2
+ from .dashboard import Dashboard, MiniPage, Page, Directory
3
3
 
4
4
  # Optionally, define __all__ to control what gets imported with `from staticdash import *`
5
- __all__ = ["Dashboard", "MiniPage", "Page"]
5
+ __all__ = ["Dashboard", "MiniPage", "Page", "Directory"]
@@ -1,6 +1,7 @@
1
1
  import os
2
2
  import shutil
3
3
  import uuid
4
+ import re
4
5
  import pandas as pd
5
6
  import plotly.graph_objects as go
6
7
  from dominate import document
@@ -314,20 +315,31 @@ class Dashboard:
314
315
  head.add(link(rel="stylesheet", href=f"{rel_prefix}assets/css/style.css"))
315
316
  head.add(script(type="text/javascript", src=f"{rel_prefix}assets/js/script.js"))
316
317
 
317
- # MathJax: config for $...$ and $$...$$, then **SVG** bundle (no webfonts needed)
318
+ # MathJax: config for $...$ and $$...$$
318
319
  head.add(raw_util(
319
320
  "<script>window.MathJax={tex:{inlineMath:[['$','$'],['\\\\(','\\\\)']],displayMath:[['$$','$$'],['\\\\[','\\\\]']]}};</script>"
320
321
  ))
321
- head.add(script(src=f"{rel_prefix}assets/vendor/mathjax/tex-svg.js"))
322
+ # Local-first, CDN-fallback (for editable installs without vendored files)
323
+ head.add(raw_util(
324
+ f"<script src=\"{rel_prefix}assets/vendor/mathjax/tex-svg.js\" "
325
+ "onerror=\"var s=document.createElement('script');"
326
+ "s.src='https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js';"
327
+ "document.head.appendChild(s);\"></script>"
328
+ ))
322
329
 
323
- # Prism (theme + core + languages) — all local
330
+ # Prism (theme + core + languages) — still local
324
331
  head.add(link(rel="stylesheet", href=f"{rel_prefix}assets/vendor/prism/prism-tomorrow.min.css"))
325
332
  head.add(script(src=f"{rel_prefix}assets/vendor/prism/prism.min.js"))
326
333
  head.add(script(src=f"{rel_prefix}assets/vendor/prism/components/prism-python.min.js"))
327
334
  head.add(script(src=f"{rel_prefix}assets/vendor/prism/components/prism-javascript.min.js"))
328
335
 
329
- # Plotly local bundle (figs use include_plotlyjs=False)
330
- head.add(script(src=f"{rel_prefix}assets/vendor/plotly/plotly.min.js"))
336
+ # Plotly local-first, CDN-fallback
337
+ head.add(raw_util(
338
+ f"<script src=\"{rel_prefix}assets/vendor/plotly/plotly.min.js\" "
339
+ "onerror=\"var s=document.createElement('script');"
340
+ "s.src='https://cdn.plot.ly/plotly-2.32.0.min.js';"
341
+ "document.head.appendChild(s);\"></script>"
342
+ ))
331
343
 
332
344
  # Defaults that match your CSS; override in CSS if they change
333
345
  head.add(raw_util("<style>:root{--sidebar-width:240px;--content-padding-x:20px;}</style>"))
@@ -390,3 +402,224 @@ class Dashboard:
390
402
 
391
403
  with open(os.path.join(output_dir, "index.html"), "w", encoding="utf-8") as f:
392
404
  f.write(str(index_doc))
405
+
406
+
407
+ class Directory:
408
+ """
409
+ A Directory aggregates multiple Dashboard instances and publishes them
410
+ as a landing page listing multiple dashboards. Each dashboard is published
411
+ into its own subfolder under the output directory.
412
+ """
413
+
414
+ def __init__(self, title="Dashboard Directory", page_width=900):
415
+ """
416
+ Initialize a Directory.
417
+
418
+ Args:
419
+ title (str): The title of the directory landing page
420
+ page_width (int): The default page width for the landing page
421
+ """
422
+ self.title = title
423
+ self.page_width = page_width
424
+ self.dashboards = [] # List of (slug, dashboard) tuples
425
+
426
+ def add_dashboard(self, dashboard, slug=None):
427
+ """
428
+ Add a Dashboard instance to the directory.
429
+
430
+ Args:
431
+ dashboard (Dashboard): The Dashboard instance to add
432
+ slug (str, optional): URL-friendly identifier for the dashboard.
433
+ If None, derived from dashboard title.
434
+ """
435
+ if slug is None:
436
+ # Generate slug from dashboard title
437
+ slug = dashboard.title.lower().replace(" ", "-")
438
+ # Remove special characters
439
+ slug = "".join(c for c in slug if c.isalnum() or c == "-")
440
+ # Clean up multiple consecutive hyphens
441
+ slug = re.sub(r'-+', '-', slug)
442
+ # Remove leading/trailing hyphens
443
+ slug = slug.strip("-")
444
+
445
+ self.dashboards.append((slug, dashboard))
446
+
447
+ def publish(self, output_dir="output"):
448
+ """
449
+ Publish the directory landing page and all dashboards.
450
+
451
+ Creates a landing page (index.html) that links to each dashboard,
452
+ and publishes each dashboard into its own subfolder.
453
+
454
+ Args:
455
+ output_dir (str): The output directory path
456
+ """
457
+ output_dir = os.path.abspath(output_dir)
458
+ os.makedirs(output_dir, exist_ok=True)
459
+
460
+ # Copy assets to the root output directory
461
+ assets_src = os.path.join(os.path.dirname(__file__), "assets")
462
+ assets_dst = os.path.join(output_dir, "assets")
463
+ shutil.copytree(assets_src, assets_dst, dirs_exist_ok=True)
464
+
465
+ # Publish each dashboard to its own subfolder
466
+ for slug, dashboard in self.dashboards:
467
+ dashboard_dir = os.path.join(output_dir, slug)
468
+ dashboard.publish(output_dir=dashboard_dir)
469
+
470
+ # Add a "Back to Directory" link to each dashboard's index page
471
+ self._add_back_link(dashboard_dir, slug)
472
+
473
+ # Create the landing page
474
+ self._create_landing_page(output_dir)
475
+
476
+ def _add_back_link(self, dashboard_dir, slug):
477
+ """
478
+ Add a navigation link back to the directory landing page in the dashboard.
479
+
480
+ Args:
481
+ dashboard_dir (str): Path to the dashboard output directory
482
+ slug (str): The slug of the dashboard
483
+ """
484
+ index_path = os.path.join(dashboard_dir, "index.html")
485
+ if not os.path.exists(index_path):
486
+ return
487
+
488
+ # Read the existing index.html
489
+ with open(index_path, "r", encoding="utf-8") as f:
490
+ content = f.read()
491
+
492
+ # Add a back link in the sidebar footer
493
+ # Replace the sidebar-footer section with one that includes a back link
494
+ back_link = '<div id="sidebar-footer"><a href="../index.html">← Back to Directory</a></div>'
495
+
496
+ # Find and replace the sidebar-footer
497
+ pattern = r'<div id="sidebar-footer">.*?</div>'
498
+ content = re.sub(pattern, back_link, content, flags=re.DOTALL)
499
+
500
+ # Write back the modified content
501
+ with open(index_path, "w", encoding="utf-8") as f:
502
+ f.write(content)
503
+
504
+ # Also update all page HTML files to have the back link
505
+ pages_dir = os.path.join(dashboard_dir, "pages")
506
+ if os.path.exists(pages_dir):
507
+ for page_file in os.listdir(pages_dir):
508
+ if page_file.endswith(".html"):
509
+ page_path = os.path.join(pages_dir, page_file)
510
+ with open(page_path, "r", encoding="utf-8") as f:
511
+ page_content = f.read()
512
+
513
+ # For pages, the back link needs to go up two levels
514
+ back_link_pages = '<div id="sidebar-footer"><a href="../../index.html">← Back to Directory</a></div>'
515
+ page_content = re.sub(pattern, back_link_pages, page_content, flags=re.DOTALL)
516
+
517
+ with open(page_path, "w", encoding="utf-8") as f:
518
+ f.write(page_content)
519
+
520
+ def _create_landing_page(self, output_dir):
521
+ """
522
+ Create the landing page HTML that lists all dashboards.
523
+
524
+ Args:
525
+ output_dir (str): Path to the output directory
526
+ """
527
+ doc = document(title=self.title)
528
+
529
+ # Add CSS and basic styling
530
+ with doc.head:
531
+ link(rel="stylesheet", href="assets/css/style.css")
532
+ raw_util("""
533
+ <style>
534
+ body {
535
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
536
+ margin: 0;
537
+ padding: 0;
538
+ background-color: #f5f5f5;
539
+ }
540
+ .directory-container {
541
+ max-width: """ + str(self.page_width) + """px;
542
+ margin: 0 auto;
543
+ padding: 40px 20px;
544
+ }
545
+ .directory-header {
546
+ text-align: center;
547
+ margin-bottom: 50px;
548
+ }
549
+ .directory-header h1 {
550
+ font-size: 2.5em;
551
+ margin-bottom: 10px;
552
+ color: #333;
553
+ }
554
+ .directory-header p {
555
+ font-size: 1.2em;
556
+ color: #666;
557
+ }
558
+ .dashboard-grid {
559
+ display: grid;
560
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
561
+ gap: 30px;
562
+ margin-top: 30px;
563
+ }
564
+ .dashboard-card {
565
+ background: white;
566
+ border-radius: 8px;
567
+ padding: 30px;
568
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
569
+ transition: transform 0.2s, box-shadow 0.2s;
570
+ text-decoration: none;
571
+ color: inherit;
572
+ display: block;
573
+ }
574
+ .dashboard-card:hover {
575
+ transform: translateY(-4px);
576
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
577
+ }
578
+ .dashboard-card h2 {
579
+ margin: 0 0 10px 0;
580
+ font-size: 1.5em;
581
+ color: #2c3e50;
582
+ }
583
+ .dashboard-card p {
584
+ margin: 0;
585
+ color: #7f8c8d;
586
+ font-size: 0.95em;
587
+ }
588
+ .dashboard-arrow {
589
+ display: inline-block;
590
+ margin-left: 5px;
591
+ transition: transform 0.2s;
592
+ }
593
+ .dashboard-card:hover .dashboard-arrow {
594
+ transform: translateX(5px);
595
+ }
596
+ .footer {
597
+ text-align: center;
598
+ margin-top: 60px;
599
+ padding: 20px;
600
+ color: #999;
601
+ font-size: 0.9em;
602
+ }
603
+ </style>
604
+ """)
605
+
606
+ with doc:
607
+ with div(cls="directory-container"):
608
+ with div(cls="directory-header"):
609
+ h1(self.title)
610
+ p(f"Explore {len(self.dashboards)} dashboard{'s' if len(self.dashboards) != 1 else ''}")
611
+
612
+ with div(cls="dashboard-grid"):
613
+ for slug, dashboard in self.dashboards:
614
+ with a(href=f"{slug}/index.html", cls="dashboard-card"):
615
+ h2(dashboard.title)
616
+ num_pages = len(dashboard.pages)
617
+ p(f"{num_pages} page{'s' if num_pages != 1 else ''} ")
618
+ span("→", cls="dashboard-arrow")
619
+
620
+ with div(cls="footer"):
621
+ p("Produced by staticdash")
622
+
623
+ # Write the landing page
624
+ with open(os.path.join(output_dir, "index.html"), "w", encoding="utf-8") as f:
625
+ f.write(str(doc))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: staticdash
3
- Version: 2025.32
3
+ Version: 2025.34
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
File without changes
File without changes
File without changes