diff --git a/kobrax_moonraker_bridge.py b/kobrax_moonraker_bridge.py index 35d860d..bd05801 100644 --- a/kobrax_moonraker_bridge.py +++ b/kobrax_moonraker_bridge.py @@ -17,6 +17,8 @@ import hashlib import json import logging import os +import pathlib +import re import sys import tempfile import time @@ -56,8 +58,9 @@ KLIPPER_VERSION = "v0.12.0-1" class KobraXBridge: - def __init__(self, client: KobraXClient): + def __init__(self, client: KobraXClient, args=None): self.client = client + self._args = args self.ws_clients: set[web.WebSocketResponse] = set() self._last_state: dict = {} self._state = { @@ -66,6 +69,7 @@ class KobraXBridge: "bed_temp": 0.0, "bed_target": 0.0, "print_state": "standby", + "kobra_state": "free", "filename": "", "progress": 0.0, "print_duration": 0, @@ -110,6 +114,8 @@ class KobraXBridge: d = payload.get("data") or {} kobra_state = payload.get("state", "") self._state["print_state"] = KOBRA_TO_KLIPPER_STATE.get(kobra_state, "printing") + if kobra_state: + self._state["kobra_state"] = kobra_state self._state["filename"] = d.get("filename", self._state["filename"]) if "progress" in d: self._state["progress"] = float(d["progress"]) / 100.0 @@ -128,6 +134,7 @@ class KobraXBridge: kobra_state = d.get("state", "") if kobra_state: self._state["print_state"] = KOBRA_TO_KLIPPER_STATE.get(kobra_state, "standby") + self._state["kobra_state"] = kobra_state t = d.get("temp") or {} if t: self._state["nozzle_temp"] = float(t.get("curr_nozzle_temp", 0)) @@ -630,6 +637,7 @@ main{flex:1;overflow-y:auto;padding:20px} /* ── TEMPS ── */ .temp-pair{display:grid;grid-template-columns:1fr 1fr;gap:12px} +.temp-card-inner{display:grid;grid-template-columns:1fr 1fr;gap:12px} .temp-block{background:var(--raised);border-radius:10px;padding:14px;position:relative} .temp-label{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--txt2);margin-bottom:6px} .temp-row{display:flex;align-items:baseline;gap:6px} @@ -702,6 +710,35 @@ canvas.tchart{width:100%;height:60px;display:block;border-radius:6px;background: .panel{display:none} .panel.active{display:block} +/* ── MODAL ── */ +.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.6); + z-index:200;align-items:center;justify-content:center;padding:16px} +.modal-overlay.open{display:flex} +.modal-box{background:var(--card);border:1px solid var(--border);border-radius:14px; + width:100%;max-width:480px;max-height:90vh;overflow-y:auto;padding:24px; + display:flex;flex-direction:column;gap:18px} +.modal-header{display:flex;align-items:center;justify-content:space-between} +.modal-title{font-size:15px;font-weight:700;color:var(--txt)} +.modal-close{background:none;border:none;color:var(--txt2);font-size:20px; + cursor:pointer;padding:4px 8px;border-radius:6px} +.modal-close:hover{background:var(--raised);color:var(--txt)} +.modal-section{font-size:10px;text-transform:uppercase;letter-spacing:.1em; + color:var(--txt2);margin-bottom:6px;margin-top:4px} +.modal-field{display:flex;flex-direction:column;gap:4px;margin-bottom:10px} +.modal-field label{font-size:12px;color:var(--txt2)} +.modal-field input{background:var(--raised);border:1px solid var(--border); + border-radius:7px;color:var(--txt);padding:7px 10px;font-size:13px;width:100%} +.modal-field input:focus{outline:none;border-color:var(--accent)} +.poll-btns{display:flex;gap:8px} +.poll-btn{flex:1;padding:7px;background:var(--raised);border:1px solid var(--border); + border-radius:7px;color:var(--txt2);cursor:pointer;font-size:13px;transition:all .15s} +.poll-btn.active{background:var(--accent);border-color:var(--accent);color:#000;font-weight:600} +.update-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap} +.update-status{font-size:12px;color:var(--txt2);flex:1;min-width:0} +.modal-save{width:100%;padding:10px;background:var(--accent);border:none; + border-radius:8px;color:#000;font-weight:700;font-size:14px;cursor:pointer;margin-top:4px} +.modal-save:hover{opacity:.88} + /* ── BOTTOM NAV (mobile) ── */ nav.bottom-nav{display:none;position:fixed;bottom:0;left:0;right:0; background:var(--card);border-top:1px solid var(--border); @@ -711,20 +748,52 @@ nav.bottom-nav{display:none;position:fixed;bottom:0;left:0;right:0; .bnav-btn.active{color:var(--accent)} .bnav-icon{font-size:20px} -@media(max-width:768px){ - nav.sidebar{display:none} - nav.bottom-nav{display:flex} - main{padding:12px;padding-bottom:72px} - .hero{grid-template-columns:1fr} - .temp-pair{grid-template-columns:1fr} - .ams-slots{grid-template-columns:repeat(2,1fr)} - .joypad{grid-template-columns:repeat(3,48px);grid-template-rows:repeat(3,48px)} -} -@media(min-width:769px) and (max-width:1200px){ +/* ── Tablet (769–1100px): schmale Sidebar ── */ +@media(min-width:769px) and (max-width:1100px){ nav.sidebar{width:52px;padding:12px 4px} .nav-btn .nav-text{display:none} .nav-btn{justify-content:center;padding:10px} .nav-icon{width:auto} + .grid{grid-template-columns:repeat(2,1fr)} + .hero{grid-template-columns:1fr} +} + +/* ── Mobile (≤768px): Bottom-Nav, 1-Spalte ── */ +@media(max-width:768px){ + nav.sidebar{display:none} + nav.bottom-nav{display:flex} + main{padding:10px;padding-bottom:72px} + + /* Header kompakt */ + header{padding:0 12px;gap:8px} + .hname{display:none} + + /* 1-Spalten-Grid, full-width spans funktionieren weiterhin */ + .grid{grid-template-columns:1fr;gap:12px} + + /* Hero: Kamera über Info */ + .hero{grid-template-columns:1fr} + .cam-wrap{max-height:220px} + + /* Temp-Pair und Temp-Card übereinander */ + .temp-pair{grid-template-columns:1fr} + .temp-card-inner{grid-template-columns:1fr} + + /* AMS: 2 Spalten */ + .ams-slots{grid-template-columns:repeat(2,1fr)} + + /* Joypad etwas kleiner */ + .joypad{grid-template-columns:repeat(3,44px);grid-template-rows:repeat(3,44px);gap:5px} + .joy{font-size:16px} + + /* Buttons größere Touch-Targets */ + .btn{padding:10px 14px;font-size:13px} + .btn-sm{padding:8px 12px} + .step-btn{padding:8px 12px;font-size:13px} + + /* Modal vollbreite auf kleinen Screens */ + .modal-box{padding:16px;border-radius:10px} + .poll-btns{gap:6px} } @@ -736,22 +805,74 @@ nav.bottom-nav{display:none;position:fixed;bottom:0;left:0;right:0;