Jetzt/Topbar und Material-Routing, Pause bei Filament-Ende, Druckbericht mit Stromanteilen, Design- und Kamera-Stream-Polish; Bedarf-Alert-Spam und Platte-belegt-Bugs behoben.
208 lines
5.9 KiB
Python
208 lines
5.9 KiB
Python
"""Lucide-Icons als lokales SVG-Sprite bauen.
|
|
|
|
python scripts/build_icons.py
|
|
|
|
Schreibt web/static/icons.svg. IDs bleiben i-<grotti-name> (bestehende Templates).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import urllib.error
|
|
import urllib.request
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
OUT = ROOT / "web" / "static" / "icons.svg"
|
|
CDN = "https://unpkg.com/[email protected]/icons/{name}.svg"
|
|
|
|
# grotti-id → lucide dateiname (ohne .svg)
|
|
ICONS: dict[str, str] = {
|
|
# bestehend
|
|
"printer": "printer",
|
|
"camera": "camera",
|
|
"file": "file",
|
|
"spool": "disc-3",
|
|
"folder": "folder",
|
|
"folder-open": "folder-open",
|
|
"list": "list",
|
|
"activity": "activity",
|
|
"clock": "clock",
|
|
"check": "check",
|
|
"alert": "triangle-alert",
|
|
"flame": "flame",
|
|
"pause": "pause",
|
|
"play": "play",
|
|
"stop": "square",
|
|
"layers": "layers",
|
|
"x": "x",
|
|
"up": "chevron-up",
|
|
"down": "chevron-down",
|
|
"upload": "upload",
|
|
"refresh": "refresh-cw",
|
|
"queue": "list-ordered",
|
|
"send": "send",
|
|
"book": "book-open",
|
|
"cube": "box",
|
|
"bell": "bell",
|
|
"sweep": "brush",
|
|
"thermo": "thermometer",
|
|
"settings": "settings",
|
|
"bulb": "lightbulb",
|
|
"search": "search",
|
|
"filter": "filter",
|
|
"info": "info",
|
|
"scan": "scan-line",
|
|
"trash": "trash-2",
|
|
"inbox": "inbox",
|
|
"image": "image",
|
|
"grid": "layout-grid",
|
|
"menu": "menu",
|
|
"panel": "panel-left",
|
|
"nozzle": "pipette",
|
|
"bed": "rectangle-horizontal",
|
|
"chamber": "box",
|
|
"fan": "fan",
|
|
"fan-chamber": "air-vent",
|
|
"fan-aux": "wind",
|
|
# neu für Polish
|
|
"plus": "plus",
|
|
"minus": "minus",
|
|
"edit": "pencil",
|
|
"save": "save",
|
|
"copy": "copy",
|
|
"external": "external-link",
|
|
"user": "user",
|
|
"users": "users",
|
|
"key": "key",
|
|
"lock": "lock",
|
|
"unlock": "lock-open",
|
|
"wifi": "wifi",
|
|
"wifi-off": "wifi-off",
|
|
"power": "power",
|
|
"zap": "zap",
|
|
"gauge": "gauge",
|
|
"chart": "chart-column",
|
|
"calendar": "calendar",
|
|
"timer": "timer",
|
|
"weight": "weight",
|
|
"package": "package",
|
|
"tag": "tag",
|
|
"hash": "hash",
|
|
"link": "link",
|
|
"download": "download",
|
|
"more": "ellipsis",
|
|
"chevron-right": "chevron-right",
|
|
"chevron-left": "chevron-left",
|
|
"home": "house",
|
|
"wrench": "wrench",
|
|
"hard-drive": "hard-drive",
|
|
"server": "server",
|
|
"message": "message-circle",
|
|
"mail": "mail",
|
|
"shield": "shield",
|
|
"sliders": "sliders-horizontal",
|
|
"palette": "palette",
|
|
"moon": "moon",
|
|
"sun": "sun",
|
|
"monitor": "monitor",
|
|
"smartphone": "smartphone",
|
|
"keyboard": "keyboard",
|
|
"undo": "undo-2",
|
|
"redo": "redo-2",
|
|
"circle-check": "circle-check",
|
|
"circle-x": "circle-x",
|
|
"circle-alert": "circle-alert",
|
|
"loader": "loader-circle",
|
|
"sparkles": "sparkles",
|
|
"clipboard": "clipboard-list",
|
|
"receipt": "receipt",
|
|
"truck": "truck",
|
|
"droplet": "droplet",
|
|
"percent": "percent",
|
|
"euro": "euro",
|
|
"arrow-up": "arrow-up",
|
|
"arrow-down": "arrow-down",
|
|
"history": "history",
|
|
"eye": "eye",
|
|
"eye-off": "eye-off",
|
|
"star": "star",
|
|
"heart": "heart",
|
|
"ban": "ban",
|
|
"rotate-ccw": "rotate-ccw",
|
|
"brush": "brush",
|
|
}
|
|
|
|
|
|
def fetch(name: str) -> str:
|
|
url = CDN.format(name=name)
|
|
req = urllib.request.Request(url, headers={"User-Agent": "Grotti-icon-build/1"})
|
|
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
return resp.read().decode("utf-8")
|
|
|
|
|
|
def to_symbol(grotti: str, svg: str) -> str:
|
|
# Lucide: optional comment + <svg ...>paths</svg>
|
|
text = re.sub(r"<!--.*?-->", "", svg, flags=re.DOTALL).strip()
|
|
match = re.search(r"<svg\b[^>]*>(.*)</svg>\s*$", text, flags=re.DOTALL | re.IGNORECASE)
|
|
if not match:
|
|
raise ValueError("kein SVG-Root")
|
|
inner = match.group(1).strip()
|
|
# verschachtelte svg-Attribute/Klassen aus Kind-Elementen nicht nötig
|
|
return (
|
|
f' <symbol id="i-{grotti}" viewBox="0 0 24 24" fill="none" '
|
|
f'stroke="currentColor" stroke-width="2" stroke-linecap="round" '
|
|
f'stroke-linejoin="round">\n {inner}\n </symbol>'
|
|
)
|
|
|
|
|
|
def main() -> None:
|
|
symbols = []
|
|
failed = []
|
|
for grotti, lucide in sorted(ICONS.items()):
|
|
try:
|
|
raw = fetch(lucide)
|
|
symbols.append(to_symbol(grotti, raw))
|
|
print("ok", grotti, "<-", lucide)
|
|
except urllib.error.HTTPError as exc:
|
|
failed.append((grotti, lucide, str(exc)))
|
|
print("FAIL", grotti, lucide, exc.code)
|
|
except Exception as exc:
|
|
failed.append((grotti, lucide, str(exc)))
|
|
print("FAIL", grotti, lucide, type(exc).__name__)
|
|
|
|
if failed and len(failed) == len(ICONS):
|
|
raise SystemExit("Keine Icons geladen.")
|
|
|
|
# Fallbacks für fehlgeschlagene: leeres symbol vermeiden — check als Ersatz
|
|
ok_ids = {s.split('id="i-')[1].split('"')[0] for s in symbols}
|
|
for grotti, lucide, _ in failed:
|
|
if grotti in ok_ids:
|
|
continue
|
|
# einfaches X als Platzhalter
|
|
symbols.append(
|
|
f' <symbol id="i-{grotti}" viewBox="0 0 24 24" fill="none" '
|
|
f'stroke="currentColor" stroke-width="2" stroke-linecap="round" '
|
|
f'stroke-linejoin="round">\n'
|
|
f' <path d="M18 6 6 18"/><path d="m6 6 12 12"/>\n </symbol>'
|
|
)
|
|
print("placeholder", grotti)
|
|
|
|
body = "\n".join(symbols)
|
|
OUT.parent.mkdir(parents=True, exist_ok=True)
|
|
OUT.write_text(
|
|
'<?xml version="1.0" encoding="UTF-8"?>\n'
|
|
f'<!-- Lucide icons, built by scripts/build_icons.py — {len(symbols)} symbols -->\n'
|
|
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">\n'
|
|
f"{body}\n"
|
|
"</svg>\n",
|
|
encoding="utf-8",
|
|
)
|
|
print("wrote", OUT, "symbols", len(symbols))
|
|
if failed:
|
|
print("warnings:", len(failed))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|