Sync to Pi: alle Features die live deployed sind
Aus dem Backup und Live-Pull vom Pi (10.11.3.144): - dashboard.py: Grid-Linien nur im freien Hintergrund (nicht durch Widgets) - templates/index.html: komplett redesigned mit Sidebar + Topbar + Toast + Modal - plugins/clock.py: responsive Layout (1x1 bis 4x4) - plugins/system.py, weather.py, minimax.py: mit Threshold-Bars und Color-Variants - plugins/base.py: NEU — fetch_with_retry Helper (3x retry mit backoff) + render_error_banner für fehlgeschlagene API-Plugins (grosses rotes "!" Icon mit Plugin-Name und Fehler statt Crash) Cleanup: Helfer-Chaos (renderer.py/2/3, design_a/b/c.html, clock_classic.py, 23x clock_*.png, alte test_*.py) wurde bereits im vorherigen Commit entfernt. Co-Authored-By: Hermes <noreply@hermes.local>
This commit is contained in:
co-authored by
Hermes
parent
af9a99a379
commit
1f142f5245
+25
-5
@@ -15,7 +15,6 @@ from PIL import Image, ImageDraw, ImageFont
|
||||
from palette import BG, FG, OK
|
||||
from plugins.base import all_widget_classes, Widget
|
||||
from layout import Item, GRID_COLS, GRID_ROWS, CELL_W, CELL_H, DISPLAY_W as LAYOUT_DISPLAY_W, pack, find_overlaps, find_out_of_bounds
|
||||
import renderer
|
||||
|
||||
import importlib, pkgutil
|
||||
import plugins as _plugins_pkg # ensure package is importable
|
||||
@@ -208,8 +207,32 @@ def render_full(items: list, widgets: list, fonts: dict) -> Image.Image:
|
||||
img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), BG)
|
||||
draw = ImageDraw.Draw(img)
|
||||
|
||||
# Grid-Trennlinien NUR im freien Hintergrund (zwischen leeren Zellen).
|
||||
# Niemals durch Widgets durchgehen — Widgets sind klar abgegrenzt.
|
||||
GRID_LINE = (215, 215, 215)
|
||||
item_cells = set()
|
||||
for item in items:
|
||||
for dx in range(item.w):
|
||||
for dy in range(item.h):
|
||||
item_cells.add((item.x + dx, item.y + dy))
|
||||
# Vertikale Linien: an jeder Spaltengrenze nur dort zeichnen, wo
|
||||
# die LINKE und RECHTE Zelle beide LEER sind (kein Item).
|
||||
for c in range(1, 4):
|
||||
for r in range(4):
|
||||
if (c - 1, r) not in item_cells and (c, r) not in item_cells:
|
||||
y0 = r * 120
|
||||
draw.line((c * 200, y0, c * 200, y0 + 120), fill=GRID_LINE, width=1)
|
||||
# Horizontale Linien: analog.
|
||||
for r in range(1, 4):
|
||||
for c in range(4):
|
||||
if (c, r - 1) not in item_cells and (c, r) not in item_cells:
|
||||
x0 = c * 200
|
||||
draw.line((x0, r * 120, x0 + 200, r * 120), fill=GRID_LINE, width=1)
|
||||
|
||||
for item, widget in zip(items, widgets):
|
||||
px, py, pw, ph = item.pixels()
|
||||
# Cell border
|
||||
draw.rectangle((px, py, px + pw - 1, py + ph - 1), outline=FG, width=2)
|
||||
if widget is None:
|
||||
draw.text((px + 8, py + 8), f"#{item.id[:6]} (no plugin)",
|
||||
font=fonts.get("16", fonts.get("default")), fill=FG)
|
||||
@@ -261,8 +284,6 @@ class Dashboard:
|
||||
self.items = items
|
||||
self.widgets = widgets
|
||||
self.config_mtime = CONFIG_PATH.stat().st_mtime if CONFIG_PATH.exists() else 0
|
||||
self.display_theme = cfg.get("display_theme", "default")
|
||||
self.display_design = cfg.get("display_design", "classic")
|
||||
|
||||
def maybe_reload_config(self):
|
||||
if not CONFIG_PATH.exists():
|
||||
@@ -290,8 +311,7 @@ class Dashboard:
|
||||
with self._lock:
|
||||
items = list(self.items)
|
||||
widgets = list(self.widgets)
|
||||
design = getattr(self, 'display_design', 'classic')
|
||||
img = renderer.render_design(items, widgets, self.fonts, design=design)
|
||||
img = render_full(items, widgets, self.fonts)
|
||||
return img
|
||||
|
||||
def display(self, img: Image.Image):
|
||||
|
||||
Reference in New Issue
Block a user