staticdash 2025.33__py3-none-any.whl → 2025.34__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/__init__.py CHANGED
@@ -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"]
staticdash/dashboard.py CHANGED
@@ -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
@@ -401,3 +402,224 @@ class Dashboard:
401
402
 
402
403
  with open(os.path.join(output_dir, "index.html"), "w", encoding="utf-8") as f:
403
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.33
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
@@ -1,5 +1,5 @@
1
- staticdash/__init__.py,sha256=UN_-h8wFGfTPHYjnEb7N9CsxqXo-DQVo0cmREOtvRXE,244
2
- staticdash/dashboard.py,sha256=lcjGzdgsRpWyS59txmQLpJT3BqNEWw2TqlzOw0gsUHs,17843
1
+ staticdash/__init__.py,sha256=MQGR6LAqx2aFEA64MZz1ADxwpXLPn3VYNVIyjt9qx4Q,268
2
+ staticdash/dashboard.py,sha256=Xuk3M8I5foTzIttJGdVyFG_z6SixoI9gTUBA3b_aCS4,26634
3
3
  staticdash/assets/css/style.css,sha256=JCoEkEzDiGc29jjhtWBMv-cirwzSWgOpCviMVIm6x2s,6533
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-2025.33.dist-info/METADATA,sha256=wLNa2jFtnKbsbvsdudn41_rJ07atJ_V6pRJudkiOi64,1960
17
- staticdash-2025.33.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
18
- staticdash-2025.33.dist-info/top_level.txt,sha256=3MzZU6SptkUkjcHV1cvPji0H4aRzPphLHnpStgGEcxM,11
19
- staticdash-2025.33.dist-info/RECORD,,
16
+ staticdash-2025.34.dist-info/METADATA,sha256=mJOkFjEZD9CSwHdJZOvCzF7pv2O1rPIcwtDBXRWmh_I,1960
17
+ staticdash-2025.34.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
18
+ staticdash-2025.34.dist-info/top_level.txt,sha256=3MzZU6SptkUkjcHV1cvPji0H4aRzPphLHnpStgGEcxM,11
19
+ staticdash-2025.34.dist-info/RECORD,,