Compare commits
1 Commits
v0.9.26
...
feature-it
| Author | SHA1 | Date | |
|---|---|---|---|
| 697a4043bd |
@@ -1,21 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [0.9.26] – 2026-06-21
|
||||
|
||||
### Neu
|
||||
- **Italienische Sprachunterstützung** (PR #66, @Alex_M). Die Bridge-UI ist jetzt vollständig auf Italienisch verfügbar.
|
||||
|
||||
### Behoben
|
||||
- **Kamera startete immer beim Druckbeginn** (Issue #50). `camera_on_print` fehlte in der `/api/state`-Antwort — JavaScript las `undefined` und startete die Kamera unabhängig vom Setting. Jetzt korrekt im State enthalten.
|
||||
- **Auto-Leveling-Setting wurde im Moonraker-Druckpfad ignoriert** (Issue #57). `handle_print_start` las den Wert nur aus den Bridge-Args, nicht aus dem Request-Body — Dialog-Checkbox und Per-Print-Override hatten keine Wirkung. Verhält sich jetzt identisch zum direkten Druckpfad.
|
||||
- **Filament-Mapping: Freitext-Felder durch Dropdowns ersetzt** (Issue #57). Falsch getippte Vendor/Name-Kombination brach das Profil-Matching ohne Fehlermeldung; Felder sind jetzt Dropdowns (Vendor → Profil, vendor-gefiltert), sodass nur gültige Kombinationen gespeichert werden können.
|
||||
- **Dashboard zeigte generischen Materialtyp statt Profilname** (Issue #57). AMS-Slot-Karten zeigen jetzt den gemappten Profilnamen (z.B. „eSUN PLA-Basic") statt nur „PLA". Fallback auf generischen Typ wenn kein Profil gemappt ist.
|
||||
- **Ghost-Profil auf leerem Slot** (Issue #57). Verwaiste Mappings für leere Slots wurden weiterhin angezeigt; leere Slots zeigen jetzt korrekt „–".
|
||||
- **Skip-Objects-Panel fehlte im Orca-Upload-Flow** (Issue #57). Panel erscheint jetzt in allen Druckflows; bei frischem Upload fragt die Bridge `fileDetails` beim Drucker nach und pollt die Objektliste bis zu 6 Sekunden nach.
|
||||
- **Banner und Dialog erschienen gleichzeitig** (Issue #57). Settings-Save setzt jetzt den Dialog-Cancel-State zurück, sodass der Slot-Mapper nach Wechsel des Start-Print-Verhaltens zuverlässig öffnet.
|
||||
- **„Leeren" lud idle-Datei beim nächsten Poll nach** (Issue #57). Leeren setzt jetzt den lokalen State sofort zurück (`file_ready`, `filename`, `thumbnail`) und löscht alle Dialog-Sperren — Vorschaubild und Aktions-Buttons verschwinden sofort und kommen nicht zurück.
|
||||
- **Material-Matching für „PLA Silk", „Matte PLA" etc.** (PR #64, @p2l). Modifier+Basis-Muster in beliebiger Wortreihenfolge werden jetzt auf den Basis-Typ normalisiert; Dash-Varianten (PLA-CF) bleiben weiterhin korrekt inkompatibel mit ihrem Basis-Typ.
|
||||
|
||||
## [0.9.25] – 2026-06-17
|
||||
|
||||
### Behoben
|
||||
|
||||
35
CHANGELOG.md
35
CHANGELOG.md
@@ -1,40 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [0.9.26] – 2026-06-21
|
||||
|
||||
### New
|
||||
- **Italian language support** (PR #66, @Alex_M). The bridge UI is now fully
|
||||
available in Italian.
|
||||
|
||||
### Fixed
|
||||
- **Camera always started at print begin** (issue #50). `camera_on_print` was
|
||||
missing from the `/api/state` response — JavaScript read `undefined` and started
|
||||
the camera regardless of the setting. Now correctly exposed in state.
|
||||
- **Auto-leveling setting ignored in Moonraker print path** (issue #57).
|
||||
`handle_print_start` read the value only from bridge args, not from the request
|
||||
body, so the dialog checkbox and the per-print override had no effect. Now
|
||||
behaves identically to the direct print path.
|
||||
- **Filament mapping free-text fields replaced by dropdowns** (issue #57). A
|
||||
mistyped vendor/name broke profile matching silently; fields are now dropdowns
|
||||
(vendor → profile, vendor-filtered) so only valid combinations can be saved.
|
||||
- **Dashboard showed generic material type instead of profile name** (issue #57).
|
||||
AMS slot cards now display the mapped profile name (e.g. "eSUN PLA-Basic")
|
||||
instead of just "PLA". Falls back to the generic type when no profile is mapped.
|
||||
- **Ghost profile shown on empty slot** (issue #57). Stale mappings for empty
|
||||
slots were still rendered; empty slots now correctly show "–".
|
||||
- **Skip-Objects panel missing in Orca upload flow** (issue #57). Panel now
|
||||
appears in all print flows; on fresh upload the bridge requests `fileDetails`
|
||||
from the printer and retries the object list for up to 6 s.
|
||||
- **Banner and dialog appeared simultaneously** (issue #57). Settings save now
|
||||
resets the dialog cancel state so the slot mapper reliably opens after toggling
|
||||
Start Print Behavior.
|
||||
- **"Clear" reloaded idle file on next poll** (issue #57). Clear now immediately
|
||||
resets local state (`file_ready`, `filename`, `thumbnail`) and clears all dialog
|
||||
locks — the preview and action buttons disappear instantly and do not return.
|
||||
- **Material matching for "PLA Silk", "Matte PLA" etc.** (PR #64, @p2l).
|
||||
Modifier+base patterns in any word order are now normalised to the base type;
|
||||
dash-suffix variants (PLA-CF) remain correctly incompatible with their base.
|
||||
|
||||
## [0.9.25] – 2026-06-17
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -48,6 +48,4 @@ MODE_ID = get("MODE_ID", "")
|
||||
DEVICE_ID = get("DEVICE_ID", "")
|
||||
DEFAULT_AMS_SLOT = get("DEFAULT_AMS_SLOT", "auto")
|
||||
AUTO_LEVELING = int(get("AUTO_LEVELING", "1"))
|
||||
CAMERA_ON_PRINT = int(get("CAMERA_ON_PRINT", "0"))
|
||||
WEB_UPLOAD_WARNING = int(get("WEB_UPLOAD_WARNING", "1"))
|
||||
PRINT_START_DIALOG = int(get("PRINT_START_DIALOG", get("FILE_READY_DIALOG", "1")))
|
||||
|
||||
@@ -1077,25 +1077,6 @@ class KobraXBridge:
|
||||
if (not skipped and self._pending_preprint_skip
|
||||
and now <= self._pending_preprint_skip_deadline):
|
||||
return
|
||||
|
||||
# Während eines aktiven Drucks sind Skip-Zustände effektiv monoton.
|
||||
# Manche Firmware-Reports kommen zwischenzeitlich leer/teilweise zurück;
|
||||
# diese dürfen bereits bestätigte Skip-Objekte nicht aus der UI löschen.
|
||||
existing_skipped = [str(n) for n in (self._skip_state.get("skipped") or []) if n]
|
||||
existing_set = set(existing_skipped)
|
||||
incoming_skipped = [str(n) for n in (skipped or []) if n]
|
||||
incoming_set = set(incoming_skipped)
|
||||
active_print = self._state.get("print_state") in ("printing", "paused")
|
||||
if active_print and existing_set:
|
||||
if not incoming_set:
|
||||
skipped = list(existing_skipped)
|
||||
elif not incoming_set.issuperset(existing_set):
|
||||
merged = list(existing_skipped)
|
||||
for n in incoming_skipped:
|
||||
if n not in existing_set:
|
||||
merged.append(n)
|
||||
skipped = merged
|
||||
|
||||
# Pending-Lock aufheben sobald Drucker die gewünschten Objekte bestätigt
|
||||
if self._pending_preprint_skip and set(skipped) >= set(self._pending_preprint_skip):
|
||||
self._pending_preprint_skip = []
|
||||
@@ -1142,7 +1123,7 @@ class KobraXBridge:
|
||||
return False
|
||||
for i in range(max(1, int(retries))):
|
||||
try:
|
||||
if self._state.get("print_state") not in ("printing", "paused"):
|
||||
if self._state.get("kobra_state") != "printing":
|
||||
time.sleep(max(0.1, float(delay_s)))
|
||||
continue
|
||||
resp = self.client.skip_objects(wanted)
|
||||
@@ -2472,18 +2453,6 @@ class KobraXBridge:
|
||||
names = json.loads(f.get("objects_skip_parts") or "[]")
|
||||
except Exception:
|
||||
names = []
|
||||
# Noch keine Objekte im Store (frischer Orca-/Web-Upload): einmal aktiv
|
||||
# file/fileDetails beim Drucker anfragen. _on_file() füllt den Store nach,
|
||||
# das Frontend pollt diesen Endpoint und bekommt die Liste beim nächsten
|
||||
# Versuch (Issue #57 — Skip-Parität auch außerhalb des File-Browsers).
|
||||
if not names:
|
||||
fn = f.get("filename") or ""
|
||||
if fn:
|
||||
try:
|
||||
self.client.publish("file", "fileDetails",
|
||||
{"root": "local", "filename": fn}, timeout=0)
|
||||
except Exception as e:
|
||||
log.debug(f"fileDetails-Nachfrage fehlgeschlagen: {e}")
|
||||
return self._json_cors({
|
||||
"result": {
|
||||
"names": names,
|
||||
@@ -2510,47 +2479,18 @@ class KobraXBridge:
|
||||
return self._json_cors({"error": str(e)}, status=502)
|
||||
return self._json_cors({"result": "ok", "names": names})
|
||||
|
||||
def _build_skip_state_result(self) -> dict:
|
||||
"""Baut den kombinierten Skip-State für UI-Endpunkte."""
|
||||
filename = self._state.get("filename", "")
|
||||
all_objects: list[str] = []
|
||||
svg = ""
|
||||
if filename:
|
||||
try:
|
||||
f = self._store.get_file_by_name(filename)
|
||||
if f:
|
||||
all_objects = json.loads(f.get("objects_skip_parts") or "[]")
|
||||
svg = f.get("svg_image") or ""
|
||||
except Exception as e:
|
||||
log.warning(f"skip_state lookup failed: {e}")
|
||||
return {
|
||||
"objects": all_objects,
|
||||
"skipped": list(self._skip_state.get("skipped", [])),
|
||||
"svg_b64": svg,
|
||||
"ts": self._skip_state.get("ts", 0),
|
||||
"filename": filename,
|
||||
}
|
||||
|
||||
async def handle_kx_skip_query(self, request):
|
||||
"""Druck-Objektliste vom Drucker neu abfragen.
|
||||
|
||||
POST /kx/skip/query → triggert skip/query_obj, wartet kurz auf den
|
||||
async skip/report und gibt den zusammengeführten Skip-State zurück.
|
||||
POST /kx/skip/query → triggert skip/query_obj, gibt zuletzt bekannten
|
||||
Stand zurück (skip/report kommt async, Frontend pollt /kx/skip/state).
|
||||
"""
|
||||
prev_ts = int(self._skip_state.get("ts", 0) or 0)
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
await loop.run_in_executor(None, lambda: self.client.query_skip_objects())
|
||||
except Exception as e:
|
||||
return self._json_cors({"error": str(e)}, status=502)
|
||||
|
||||
deadline = time.time() + 1.5
|
||||
while time.time() < deadline:
|
||||
if int(self._skip_state.get("ts", 0) or 0) > prev_ts:
|
||||
break
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
return self._json_cors({"result": self._build_skip_state_result()})
|
||||
return self._json_cors({"result": self._skip_state})
|
||||
|
||||
async def handle_kx_skip_state(self, request):
|
||||
"""Aktueller Skip-State.
|
||||
@@ -2562,7 +2502,25 @@ class KobraXBridge:
|
||||
nicht die Gesamtliste.
|
||||
- Geskippt: aus self._skip_state (von skip/report aktualisiert).
|
||||
"""
|
||||
return self._json_cors({"result": self._build_skip_state_result()})
|
||||
filename = self._state.get("filename", "")
|
||||
all_objects: list[str] = []
|
||||
svg = ""
|
||||
if filename:
|
||||
try:
|
||||
f = self._store.get_file_by_name(filename)
|
||||
if f:
|
||||
all_objects = json.loads(f.get("objects_skip_parts") or "[]")
|
||||
svg = f.get("svg_image") or ""
|
||||
except Exception as e:
|
||||
log.warning(f"skip_state lookup failed: {e}")
|
||||
result = {
|
||||
"objects": all_objects,
|
||||
"skipped": list(self._skip_state.get("skipped", [])),
|
||||
"svg_b64": svg,
|
||||
"ts": self._skip_state.get("ts", 0),
|
||||
"filename": filename,
|
||||
}
|
||||
return self._json_cors({"result": result})
|
||||
|
||||
async def handle_kx_printers(self, request):
|
||||
# Aktive Drucker (mit IP) sammeln
|
||||
@@ -2657,8 +2615,8 @@ class KobraXBridge:
|
||||
},
|
||||
}
|
||||
|
||||
# UI erst nach echter Drucker-Bestätigung als "geskippt" markieren.
|
||||
self._skip_state = {"skipped": [], "ts": int(time.time())}
|
||||
# Pre-Print-Skip sofort im UI-Status spiegeln
|
||||
self._skip_state = {"skipped": list(excluded_objects), "ts": int(time.time())}
|
||||
if excluded_objects:
|
||||
self._pending_preprint_skip = [str(n) for n in excluded_objects if isinstance(n, str) and n]
|
||||
self._pending_preprint_skip_deadline = time.time() + 12.0
|
||||
@@ -3181,8 +3139,7 @@ class KobraXBridge:
|
||||
ams_box_mapping = self._build_auto_ams_box_mapping()
|
||||
|
||||
use_ams = len(ams_box_mapping) > 0
|
||||
# Dialog-Checkbox (body) hat Vorrang, sonst Setting-Default (wie handle_kx_print).
|
||||
auto_leveling = int(body.get("auto_leveling", getattr(self._args, "auto_leveling", 1)))
|
||||
auto_leveling = getattr(self._args, "auto_leveling", 1)
|
||||
url = self._state.get("last_upload_url", "")
|
||||
filesize = self._state.get("last_upload_size", 0)
|
||||
md5 = self._state.get("last_upload_md5", "")
|
||||
@@ -3212,15 +3169,6 @@ class KobraXBridge:
|
||||
},
|
||||
}
|
||||
|
||||
# UI erst nach echter Drucker-Bestätigung als "geskippt" markieren.
|
||||
self._skip_state = {"skipped": [], "ts": int(time.time())}
|
||||
if excluded_objects:
|
||||
self._pending_preprint_skip = [str(n) for n in excluded_objects if isinstance(n, str) and n]
|
||||
self._pending_preprint_skip_deadline = time.time() + 12.0
|
||||
else:
|
||||
self._pending_preprint_skip = []
|
||||
self._pending_preprint_skip_deadline = 0.0
|
||||
|
||||
log.info(
|
||||
f"print/start api=1 mode={self._filament_mode} "
|
||||
f"ams={len(ams_box_mapping)} slots assignments={filament_assignments is not None}"
|
||||
@@ -3233,9 +3181,6 @@ class KobraXBridge:
|
||||
if result is None:
|
||||
return web.json_response({"error": "Keine Antwort vom Drucker"}, status=504)
|
||||
|
||||
if excluded_objects:
|
||||
loop.run_in_executor(None, lambda: self._apply_preprint_skip_after_start(excluded_objects))
|
||||
|
||||
return web.json_response({"result": "ok"})
|
||||
|
||||
async def handle_print_pause(self, request):
|
||||
@@ -3870,8 +3815,6 @@ class KobraXBridge:
|
||||
"camera_url": s["camera_url"],
|
||||
"fan_speed": s["fan_speed"],
|
||||
"print_speed_mode": s["print_speed_mode"],
|
||||
"auto_leveling": getattr(self._args, "auto_leveling", 1),
|
||||
"camera_on_print": getattr(self._args, "camera_on_print", 0),
|
||||
"web_upload_warning": getattr(self._args, "web_upload_warning", 1),
|
||||
"light_on": s["light_on"],
|
||||
"light_brightness": s["light_brightness"],
|
||||
|
||||
@@ -9,10 +9,9 @@ var camOn=false;
|
||||
var camUserStopped=false; // user stopped camera manually — suppress auto-restart for this print
|
||||
var _camPollInterval=null; // snapshot-polling interval for Android (no MJPEG support)
|
||||
var _lastLoadedFile=null; // zuletzt geladene/gedruckte Datei für Progress-Karten-Aktionen (Issue #55)
|
||||
var _idleCleared=false; // User hat idle-Datei explizit „geleert" → kein Nachladen von s.filename (Issue #57)
|
||||
var _fdDialogOpen=false; // Dialog ist gerade offen
|
||||
var _fdAutoOpenedFile=sessionStorage.getItem('fdAutoOpenedFile')||null;
|
||||
var _fdUserCancelled=sessionStorage.getItem('fdUserCancelled')==='1';
|
||||
var _fdAutoOpenedFile=null; // Dateiname für den der Dialog auto-geöffnet wurde
|
||||
var _fdUserCancelled=false; // User hat den Auto-Open-Dialog abgebrochen
|
||||
var currentStep=1;
|
||||
var currentPanel='dashboard';
|
||||
var aceAutoRefillPrefs=(function(){
|
||||
@@ -666,18 +665,10 @@ function applyState(){
|
||||
if(s.file_ready&&s.print_state==='standby'){
|
||||
document.getElementById('file-ready-name').textContent=s.file_ready;
|
||||
// Neue Datei → Abbruch-Sperre aufheben
|
||||
if(_fdAutoOpenedFile&&_fdAutoOpenedFile!==s.file_ready){
|
||||
_fdUserCancelled=false;
|
||||
sessionStorage.removeItem('fdUserCancelled');
|
||||
sessionStorage.removeItem('fdAutoOpenedFile');
|
||||
}
|
||||
if(shouldAutoOpen){
|
||||
// Dialog-Modus: Banner niemals anzeigen.
|
||||
frb.style.display='none';
|
||||
if(!_fdDialogOpen&&!_fdUserCancelled&&_fdAutoOpenedFile!==s.file_ready){
|
||||
_fdAutoOpenedFile=s.file_ready;
|
||||
startReadyFileWithSlots(s.file_ready,true);
|
||||
}
|
||||
if(_fdAutoOpenedFile&&_fdAutoOpenedFile!==s.file_ready) _fdUserCancelled=false;
|
||||
if(shouldAutoOpen&&!_fdDialogOpen&&!_fdUserCancelled&&_fdAutoOpenedFile!==s.file_ready){
|
||||
_fdAutoOpenedFile=s.file_ready;
|
||||
startReadyFileWithSlots(s.file_ready,true);
|
||||
} else {
|
||||
frb.style.display='flex';
|
||||
bannerVisible=true;
|
||||
@@ -696,11 +687,8 @@ function applyState(){
|
||||
// Zuletzt geladene Datei merken (Issue #55): solange sie über den State
|
||||
// sichtbar ist. Beim Druckende/Abbruch leert die Bridge file_ready+filename
|
||||
// (Issue #29) — die gemerkte Referenz bleibt für die Karten-Aktionen.
|
||||
// Echte ready-Datei oder laufender Druck hebt einen vorherigen „Clear" auf.
|
||||
if(s.file_ready||printing) _idleCleared=false;
|
||||
if(s.file_ready) _lastLoadedFile=s.file_ready;
|
||||
else if(s.filename && !_idleCleared) _lastLoadedFile=s.filename;
|
||||
else if(_idleCleared) _lastLoadedFile=null;
|
||||
else if(s.filename) _lastLoadedFile=s.filename;
|
||||
// Idle-Aktionen (Drucken/Slots/Leeren) nur wenn nicht gedruckt wird, eine
|
||||
// Datei bekannt ist und der grüne Banner nicht ohnehin schon dieselbe Aktion
|
||||
// anbietet.
|
||||
@@ -877,13 +865,7 @@ function applyState(){
|
||||
var activity=(slot.activity||'');
|
||||
var pct=empty?T.ams_empty:(slot.consumables_percent!=null?slot.consumables_percent+'%':'–');
|
||||
var slotLabel=T.label_slot+' '+(globalIdx+1);
|
||||
// Gemapptes Profil nur für belegte Slots verwenden — sonst zeigt ein
|
||||
// verwaistes Mapping (Slot wurde geleert) ein „Geister"-Profil (Issue #57).
|
||||
var profile=empty?null:(window._slotProfileMap||{})[globalIdx];
|
||||
var genericType=(slot.type||slot.material_type||'–');
|
||||
// Material-Label: bei belegtem Slot mit Mapping den konkreten Profilnamen
|
||||
// (z.B. „eSUN PLA+") statt nur des generischen Typs zeigen (Issue #57 Punkt 4).
|
||||
var materialLabel=empty?'–':((profile&&profile.name)?profile.name:genericType);
|
||||
var profile=(window._slotProfileMap||{})[globalIdx];
|
||||
var vendorBadge='';
|
||||
if(!empty && profile && profile.vendor){
|
||||
var tt=(profile.name||'')+(profile.id?' ('+profile.id+')':'');
|
||||
@@ -892,7 +874,7 @@ function applyState(){
|
||||
html+='<div class="ams-slot'+(active?' active':'')+(loaded?' loaded':'')+(activity?' '+activity:'')+(empty?' empty':'')
|
||||
+'" style="--slot-color:'+col+';opacity:'+(empty?0.4:1)+';cursor:pointer" onclick="openSlotEdit('+i+')">'
|
||||
+'<div class="slot-circle" style="background:'+col+'"></div>'
|
||||
+'<div class="slot-material" title="'+(empty?'':genericType)+'">'+materialLabel+'</div>'
|
||||
+'<div class="slot-material">'+(empty?'–':(slot.type||slot.material_type||'–'))+'</div>'
|
||||
+vendorBadge
|
||||
+'<div class="slot-label">'+slotLabel+'</div>'
|
||||
+'<div class="slot-label" style="font-size:10px;color:var(--txt2)">'+pct+'</div>'
|
||||
@@ -914,7 +896,7 @@ function applyState(){
|
||||
if(co)co.style.display=(s.print_state==='printing'&&camOn)?'block':'none';
|
||||
|
||||
// auto-start camera during print (unless user explicitly stopped it)
|
||||
if(s.print_state==='printing'&&!camOn&&s.camera_url&&!camUserStopped&&s.camera_on_print){
|
||||
if(s.print_state==='printing'&&!camOn&&s.camera_url&&!camUserStopped){
|
||||
camStart();
|
||||
}
|
||||
// reset user-stopped flag when print ends so next print auto-starts again
|
||||
@@ -1030,11 +1012,6 @@ function onPollIntervalInput(){
|
||||
}
|
||||
|
||||
// ── Filament-Profil-Mapping pro Slot ([filament_profiles]) ──
|
||||
// Pro Slot ein einzelnes Profil-Dropdown (vendor+name gemeinsam, gekeyt per
|
||||
// _profileKey). Kein Freitext mehr → das (vendor,name)→id-Matching kann nicht
|
||||
// mehr durch manuelle Eingabe brechen (Issue #57 Punkt 1). Optionen werden aus
|
||||
// /kx/filament/profiles geladen, nach Vendor gruppiert, User-Profile zuerst,
|
||||
// mit demselben Vendor-Sichtbarkeitsfilter wie das Slot-Edit-Dropdown.
|
||||
function renderFilamentMapping(map){
|
||||
var el=document.getElementById('filament-mapping-list');
|
||||
if(!el)return;
|
||||
@@ -1044,61 +1021,21 @@ function renderFilamentMapping(map){
|
||||
var idHint=m.id?' <span style="color:var(--txt2);font-size:11px">('+m.id+')</span>':'';
|
||||
rows+='<div class="modal-field" style="margin-bottom:8px">'
|
||||
+'<label>Slot '+(i+1)+idHint+'</label>'
|
||||
+'<select id="fmap-'+i+'" data-vendor="'+(m.vendor||'')+'" data-name="'+(m.name||'')+'" style="width:100%"></select>'
|
||||
+'</div>';
|
||||
+'<div style="display:flex;gap:6px;flex-wrap:wrap">'
|
||||
+'<input type="text" id="fmap-'+i+'-vendor" placeholder="Vendor (z.B. Polymaker)" value="'+(m.vendor||'')+'" style="flex:1;min-width:120px">'
|
||||
+'<input type="text" id="fmap-'+i+'-name" placeholder="Name (z.B. PolyTerra PLA)" value="'+(m.name||'')+'" style="flex:1;min-width:140px">'
|
||||
+'</div></div>';
|
||||
}
|
||||
el.innerHTML=rows;
|
||||
// Dropdowns befüllen (async, geteilter Profil-Cache + Vendor-Filter)
|
||||
for(var j=0;j<4;j++){ _fillMappingDropdown(j); }
|
||||
}
|
||||
function _fillMappingDropdown(slot){
|
||||
var sel=document.getElementById('fmap-'+slot);
|
||||
if(!sel) return;
|
||||
var wantKey=_profileKey(sel.dataset.vendor, sel.dataset.name);
|
||||
_loadOrcaFilaments(function(profiles){
|
||||
sel.innerHTML='<option value="">'+(tr('slot_edit_profile_default')||'Generic (Standard)')+'</option>';
|
||||
var userProfs=profiles.filter(function(p){return p.is_user;});
|
||||
var systemProfs=profiles.filter(function(p){return !p.is_user;});
|
||||
function _opt(g,p){
|
||||
var o=document.createElement('option');
|
||||
o.value=_profileKey(p.vendor,p.name);
|
||||
o.dataset.vendor=p.vendor; o.dataset.name=p.name; o.dataset.id=p.id||'';
|
||||
o.textContent=(p.is_user?'★ ':'')+p.name+(p.vendor?' — '+p.vendor:'');
|
||||
if(o.value===wantKey)o.selected=true;
|
||||
g.appendChild(o);
|
||||
}
|
||||
if(userProfs.length){
|
||||
var gUser=document.createElement('optgroup');
|
||||
gUser.label='★ '+(tr('orca_profile_user_label')||'Eigene Profile');
|
||||
userProfs.forEach(function(p){_opt(gUser,p);});
|
||||
sel.appendChild(gUser);
|
||||
}
|
||||
_loadVisibleVendors(function(vis){
|
||||
var filtered=systemProfs;
|
||||
if(vis&&vis.length){
|
||||
var allow={};vis.forEach(function(v){allow[v]=1;});allow['Generic']=1;
|
||||
filtered=systemProfs.filter(function(p){return allow[p.vendor];});
|
||||
}
|
||||
var byVendor={};
|
||||
filtered.forEach(function(p){(byVendor[p.vendor]=byVendor[p.vendor]||[]).push(p);});
|
||||
Object.keys(byVendor).sort().forEach(function(v){
|
||||
var g=document.createElement('optgroup');g.label=v;
|
||||
byVendor[v].forEach(function(p){_opt(g,p);});
|
||||
sel.appendChild(g);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
function saveFilamentMapping(){
|
||||
// Nutzt den per-Slot-Endpoint (vendor,name → ID-Lookup im Backend).
|
||||
// Leere Auswahl ("") = Mapping entfernen.
|
||||
// Leere Felder = Mapping entfernen.
|
||||
var chain=Promise.resolve();
|
||||
for(var i=0;i<4;i++){
|
||||
(function(slot){
|
||||
var sel=document.getElementById('fmap-'+slot);
|
||||
var opt=sel?sel.options[sel.selectedIndex]:null;
|
||||
var vendor=(opt&&opt.dataset.vendor)||'';
|
||||
var name=(opt&&opt.dataset.name)||'';
|
||||
var vendor=((document.getElementById('fmap-'+slot+'-vendor')||{}).value||'').trim();
|
||||
var name=((document.getElementById('fmap-'+slot+'-name')||{}).value||'').trim();
|
||||
chain=chain.then(function(){
|
||||
return fetch(_apiUrl('/kx/filament/slots/'+slot+'/profile'),
|
||||
{method:'POST',headers:{'Content-Type':'application/json'},
|
||||
@@ -1257,7 +1194,6 @@ function doProfileImportUpload(files){
|
||||
var _slotEditIndex=-1;
|
||||
var _slotEditLoaded=false;
|
||||
var _MAT_PRESETS=['PLA','PETG','ABS','ASA','TPU','PA','PC','HIPS'];
|
||||
var _BASE_MATERIAL_TYPES=['PLA','PETG','ABS','ASA','TPU','TPE','PA','PC','HIPS','PEI','PEEK'];
|
||||
function updateSlotEditFeedButton(){
|
||||
var btn=document.getElementById('btn-slot-edit-feed');
|
||||
if(!btn)return;
|
||||
@@ -1394,39 +1330,23 @@ function slotEditFeed(){
|
||||
}
|
||||
function startReadyFile(filename){
|
||||
var fn=filename||S.file_ready;
|
||||
function _doStartReadyFile(){
|
||||
var btn=document.getElementById('file-ready-btn');
|
||||
if(btn){btn.disabled=true;btn.textContent='…';}
|
||||
post('/printer/print/start',{filename:fn})
|
||||
.then(function(r){return r.json();})
|
||||
.then(function(){
|
||||
document.getElementById('file-ready-banner').style.display='none';
|
||||
if(btn){btn.disabled=false;setText('file-ready-btn',T.file_ready_btn);}
|
||||
})
|
||||
.catch(function(e){
|
||||
clog(tr('log_error')+' '+e,'msg-err');
|
||||
if(btn){btn.disabled=false;setText('file-ready-btn',T.file_ready_btn);}
|
||||
});
|
||||
}
|
||||
function _gateAndStart(fileObj){
|
||||
if(fileObj && fileObj.web_unverified && webUploadWarningEnabled()){
|
||||
maybeGateWebUpload(fileObj, function(){ startReadyFile(fn); });
|
||||
return;
|
||||
}
|
||||
_doStartReadyFile();
|
||||
}
|
||||
var currentFile=(storeFiles||[]).find(function(f){return f.filename===fn;});
|
||||
if(currentFile){
|
||||
_gateAndStart(currentFile);
|
||||
if(currentFile && currentFile.web_unverified && webUploadWarningEnabled()){
|
||||
maybeGateWebUpload(currentFile, function(){ startReadyFile(fn); });
|
||||
return;
|
||||
}
|
||||
fetch(_apiUrl('/kx/files')).then(function(r){return r.json();}).then(function(d){
|
||||
storeFiles=d.result||[];
|
||||
var refreshed=(storeFiles||[]).find(function(f){return f.filename===fn;})||null;
|
||||
_gateAndStart(refreshed);
|
||||
}).catch(function(){
|
||||
_doStartReadyFile();
|
||||
});
|
||||
var btn=document.getElementById('file-ready-btn');
|
||||
if(btn){btn.disabled=true;btn.textContent='…';}
|
||||
post('/printer/print/start',{filename:fn})
|
||||
.then(function(r){return r.json();})
|
||||
.then(function(r){
|
||||
document.getElementById('file-ready-banner').style.display='none';
|
||||
if(btn){btn.disabled=false;setText('file-ready-btn',T.file_ready_btn);}
|
||||
})
|
||||
.catch(function(e){
|
||||
clog(tr('log_error')+' '+e,'msg-err');
|
||||
if(btn){btn.disabled=false;setText('file-ready-btn',T.file_ready_btn);}
|
||||
});
|
||||
}
|
||||
function cancelReadyFile(){
|
||||
post('/api/file_ready/clear',{})
|
||||
@@ -1442,17 +1362,8 @@ function startIdleFileWithSlots(){
|
||||
}
|
||||
function clearIdleFile(){
|
||||
_lastLoadedFile=null;
|
||||
_idleCleared=true; // verhindert Nachladen von s.filename im nächsten poll() (Issue #57)
|
||||
_fdAutoOpenedFile=null; // nächster Upload derselben Datei soll Dialog wieder öffnen
|
||||
_fdUserCancelled=false;
|
||||
_fdDialogOpen=false;
|
||||
sessionStorage.removeItem('fdAutoOpenedFile');
|
||||
sessionStorage.removeItem('fdUserCancelled');
|
||||
sessionStorage.removeItem('webVerifyCancelledFileId');
|
||||
S.file_ready=''; S.filename=''; S.thumbnail=''; // sofort lokal leeren, kein Warten auf nächsten Poll
|
||||
var ib=document.getElementById('d-idle-btns');if(ib)ib.style.display='none';
|
||||
var fn=document.getElementById('d-fname');if(fn){fn.textContent='–';fn.title='';}
|
||||
var thumb=document.getElementById('d-thumbnail');if(thumb){thumb.style.display='none';thumb.src='';}
|
||||
post('/api/file_ready/clear',{}).catch(function(){});
|
||||
}
|
||||
function selectMatPreset(m){
|
||||
@@ -1551,11 +1462,6 @@ function saveSettings(){
|
||||
btn.disabled=true;btn.textContent='…';
|
||||
var webUploadWarning=(document.getElementById('s-web-upload-warning')||{}).checked?1:0;
|
||||
S.web_upload_warning=webUploadWarning;
|
||||
// Start-Print-Behavior-Wechsel könnte den Auto-Open sonst dauerhaft blockieren
|
||||
// (alter _fdUserCancelled bei gleicher file_ready) → Dialog-State zurücksetzen (Issue #57).
|
||||
_fdUserCancelled=false;_fdAutoOpenedFile=null;
|
||||
sessionStorage.removeItem('fdUserCancelled');sessionStorage.removeItem('fdAutoOpenedFile');
|
||||
sessionStorage.removeItem('webVerifyCancelledFileId');
|
||||
post('/api/settings',{
|
||||
printer_name: document.getElementById('s-printer-name').value,
|
||||
printer_ip: document.getElementById('s-printer-ip').value,
|
||||
@@ -2227,7 +2133,6 @@ var _filamentDialogMode='store'; // 'store' oder 'banner'
|
||||
var _pendingWebVerifyFileId=null;
|
||||
var _pendingWebVerifyFilename='';
|
||||
var _pendingWebVerifyAction=null;
|
||||
var _pendingWebVerifyAutoOpen=false;
|
||||
// GCode-Store-Dateiliste. MUSS deklariert sein – sonst ReferenceError, wenn
|
||||
// "Slots wählen" im Banner geklickt wird, bevor der Browser-Tab je geladen
|
||||
// wurde (Issue #29 / Theme-Auslagerung PR #27).
|
||||
@@ -2295,8 +2200,7 @@ function clearWebUploadWarningFlag(fileId, onDone){
|
||||
});
|
||||
}
|
||||
|
||||
function maybeGateWebUpload(fileObj, onContinue, opts){
|
||||
opts=opts||{};
|
||||
function maybeGateWebUpload(fileObj, onContinue){
|
||||
if(!fileObj || !fileObj.web_unverified){
|
||||
if(onContinue) onContinue();
|
||||
return;
|
||||
@@ -2305,20 +2209,15 @@ function maybeGateWebUpload(fileObj, onContinue, opts){
|
||||
if(onContinue) onContinue();
|
||||
return;
|
||||
}
|
||||
var cancelledId=sessionStorage.getItem('webVerifyCancelledFileId')||'';
|
||||
if(opts.autoOpen && cancelledId && cancelledId===String(fileObj.id||'')){
|
||||
return;
|
||||
}
|
||||
openWebVerifyDialog(fileObj.id, fileObj.filename, function(){
|
||||
clearWebUploadWarningFlag(fileObj.id, onContinue);
|
||||
}, !!opts.autoOpen);
|
||||
});
|
||||
}
|
||||
|
||||
function openWebVerifyDialog(fileId, filename, onConfirm, autoOpen){
|
||||
function openWebVerifyDialog(fileId, filename, onConfirm){
|
||||
_pendingWebVerifyFileId=fileId;
|
||||
_pendingWebVerifyFilename=filename;
|
||||
_pendingWebVerifyAction=onConfirm||null;
|
||||
_pendingWebVerifyAutoOpen=!!autoOpen;
|
||||
var status=document.getElementById('store-web-verify-status');
|
||||
if(status){status.textContent='';}
|
||||
openStoreWebVerifyDialog();
|
||||
@@ -2332,13 +2231,9 @@ function openStoreWebVerifyDialog(){
|
||||
function closeStoreWebVerifyDialog(){
|
||||
var modal=document.getElementById('store-web-verify-dialog');
|
||||
if(modal){modal.classList.remove('open');}
|
||||
if(_pendingWebVerifyAutoOpen && _pendingWebVerifyFileId){
|
||||
sessionStorage.setItem('webVerifyCancelledFileId', String(_pendingWebVerifyFileId));
|
||||
}
|
||||
_pendingWebVerifyFileId=null;
|
||||
_pendingWebVerifyFilename='';
|
||||
_pendingWebVerifyAction=null;
|
||||
_pendingWebVerifyAutoOpen=false;
|
||||
}
|
||||
|
||||
function confirmStoreWebVerify(){
|
||||
@@ -2358,11 +2253,9 @@ function confirmStoreWebVerify(){
|
||||
.then(function(){
|
||||
var fileObj=(storeFiles||[]).find(function(f){return f.id===fileId;});
|
||||
if(fileObj){fileObj.web_unverified=false;}
|
||||
sessionStorage.removeItem('webVerifyCancelledFileId');
|
||||
_pendingWebVerifyFileId=null;
|
||||
_pendingWebVerifyFilename='';
|
||||
_pendingWebVerifyAction=null;
|
||||
_pendingWebVerifyAutoOpen=false;
|
||||
closeStoreWebVerifyDialog();
|
||||
loadStore();
|
||||
if(typeof action==='function') action();
|
||||
@@ -2378,7 +2271,7 @@ function startReadyFileWithSlots(filename,_autoOpen){
|
||||
var fn=filename||S.file_ready;
|
||||
var currentFile=(storeFiles||[]).find(function(f){return f.filename===fn;});
|
||||
if(currentFile && currentFile.web_unverified && webUploadWarningEnabled()){
|
||||
maybeGateWebUpload(currentFile, function(){ startReadyFileWithSlots(fn,_autoOpen); }, {autoOpen:!!_autoOpen});
|
||||
maybeGateWebUpload(currentFile, function(){ startReadyFileWithSlots(fn); });
|
||||
return;
|
||||
}
|
||||
_filamentDialogMode='banner';
|
||||
@@ -2399,31 +2292,23 @@ function startReadyFileWithSlots(filename,_autoOpen){
|
||||
});
|
||||
}
|
||||
|
||||
function _proceedWithFileObj(fileObj){
|
||||
if(fileObj && fileObj.web_unverified && webUploadWarningEnabled()){
|
||||
// Verify-Gate war beim ersten Lookup noch nicht aktiv (storeFiles leer) — jetzt prüfen.
|
||||
if(_autoOpen){_fdDialogOpen=false;}
|
||||
maybeGateWebUpload(fileObj, function(){ startReadyFileWithSlots(fn,_autoOpen); }, {autoOpen:!!_autoOpen});
|
||||
return;
|
||||
}
|
||||
if(fileObj){
|
||||
_storeFileId=fileObj.id;
|
||||
_setGcodeFilamentsFromFileObj(fileObj);
|
||||
}
|
||||
openWithSlots();
|
||||
}
|
||||
|
||||
var fileObj=(storeFiles||[]).find(function(f){return f.filename===_storeFilename;});
|
||||
if(fileObj){
|
||||
_proceedWithFileObj(fileObj);
|
||||
_storeFileId=fileObj.id;
|
||||
_setGcodeFilamentsFromFileObj(fileObj);
|
||||
openWithSlots();
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: refresh file list, then resolve current file by filename.
|
||||
fetch(_apiUrl('/kx/files')).then(function(r){return r.json()}).then(function(d){
|
||||
storeFiles=d.result||[];
|
||||
var refreshed=(storeFiles||[]).find(function(f){return f.filename===_storeFilename;})||null;
|
||||
_proceedWithFileObj(refreshed);
|
||||
var refreshed=(storeFiles||[]).find(function(f){return f.filename===_storeFilename;});
|
||||
if(refreshed){
|
||||
_storeFileId=refreshed.id;
|
||||
_setGcodeFilamentsFromFileObj(refreshed);
|
||||
}
|
||||
openWithSlots();
|
||||
}).catch(function(){
|
||||
openWithSlots();
|
||||
});
|
||||
@@ -2448,17 +2333,6 @@ function _normalizeMaterialKey(material){
|
||||
var key=(material||'').toUpperCase().replace(/[^A-Z0-9+]/g,'');
|
||||
// Orca often uses PLA for PLA+, while AMS may report PLA+.
|
||||
if(key==='PLA+'||key==='PLAPLUS') return 'PLA';
|
||||
// Handle modifier+base patterns in either order: "Matte PLA", "Silk PETG",
|
||||
// "PLA Silk", "PLA Matte". OrcaSlicer always writes the base type in GCode
|
||||
// (filament_type = PLA), but users label slots with the full product-style name.
|
||||
var trimmed=(material||'').trim();
|
||||
if(trimmed.indexOf(' ')>=0){
|
||||
var words=trimmed.toUpperCase().split(/\s+/);
|
||||
for(var i=0;i<words.length;i++){
|
||||
var w=words[i].replace(/[^A-Z0-9+]/g,'');
|
||||
if(_BASE_MATERIAL_TYPES.indexOf(w)>=0) return w;
|
||||
}
|
||||
}
|
||||
return key;
|
||||
}
|
||||
function _materialsCompatible(a,b){
|
||||
@@ -2514,30 +2388,19 @@ function openFilamentDialog(slots){
|
||||
if(objBody)objBody.style.display='none'; // immer eingeklappt starten
|
||||
if(objArrow)objArrow.style.transform='';
|
||||
if(_storeFileId){
|
||||
// Bei frischem Orca-/Web-Upload liefert der Drucker die Objektliste
|
||||
// (objects_skip_parts) erst per fileDetails nach → ist im Store kurz leer.
|
||||
// Daher mehrfach nachfragen, bis Objekte da sind (Issue #57 Skip-Parität).
|
||||
var _objFid=_storeFileId;
|
||||
var _objTries=0;
|
||||
(function _loadObjects(){
|
||||
if(_objFid!==_storeFileId) return; // Dialog wechselte Datei → abbrechen
|
||||
fetch(_apiUrl('/kx/files/'+encodeURIComponent(_objFid)+'/objects'))
|
||||
.then(function(r){return r.json()})
|
||||
.then(function(d){
|
||||
var names=(d.result&&d.result.names)||[];
|
||||
var svg=(d.result&&d.result.svg_b64)||'';
|
||||
if(names.length>=2){
|
||||
_printObjectsSvg=svg;
|
||||
_printObjects=names.map(function(n){return {name:n,skip:false};});
|
||||
renderObjectChecklist(); renderObjectSvg();
|
||||
var cnt=document.getElementById('fd-objects-count');
|
||||
if(cnt)cnt.textContent='('+names.length+')';
|
||||
if(objSection)objSection.style.display='block';
|
||||
} else if(_objTries++ < 6){
|
||||
setTimeout(_loadObjects, 1000); // bis ~6s auf fileDetails warten
|
||||
}
|
||||
}).catch(function(){});
|
||||
})();
|
||||
fetch(_apiUrl('/kx/files/'+encodeURIComponent(_storeFileId)+'/objects'))
|
||||
.then(function(r){return r.json()})
|
||||
.then(function(d){
|
||||
var names=(d.result&&d.result.names)||[];
|
||||
_printObjectsSvg=(d.result&&d.result.svg_b64)||'';
|
||||
if(names.length>=2){
|
||||
_printObjects=names.map(function(n){return {name:n,skip:false};});
|
||||
renderObjectChecklist(); renderObjectSvg();
|
||||
var cnt=document.getElementById('fd-objects-count');
|
||||
if(cnt)cnt.textContent='('+names.length+')';
|
||||
if(objSection)objSection.style.display='block';
|
||||
}
|
||||
}).catch(function(){});
|
||||
}
|
||||
|
||||
// GCode-Kanäle: bevorzugt aus _gcodeFilaments, sonst aus belegten AMS-Slots ableiten
|
||||
@@ -2629,11 +2492,7 @@ function closeFilamentDialog(){
|
||||
var dlg=document.getElementById('filament-dialog');
|
||||
if(dlg)dlg.classList.remove('open');
|
||||
_fdDialogOpen=false;
|
||||
if(_fdAutoOpenedFile){
|
||||
_fdUserCancelled=true;
|
||||
sessionStorage.setItem('fdUserCancelled','1');
|
||||
sessionStorage.setItem('fdAutoOpenedFile',_fdAutoOpenedFile);
|
||||
}
|
||||
if(_fdAutoOpenedFile) _fdUserCancelled=true;
|
||||
}
|
||||
|
||||
function confirmFilamentPrint(){
|
||||
@@ -2677,41 +2536,19 @@ function confirmFilamentPrint(){
|
||||
var fdAutoLeveling=fdAlEl?( fdAlEl.checked?1:0):(S.auto_leveling===undefined?1:S.auto_leveling?1:0);
|
||||
closeFilamentDialog();
|
||||
if(_filamentDialogMode==='banner'){
|
||||
// Banner-Modus: /kx/print bevorzugen wenn _storeFileId bekannt (gleicher Pfad wie File-Browser).
|
||||
// Banner-Modus: normaler print/start mit Slot-Override
|
||||
var btn=document.getElementById('file-ready-btn');
|
||||
if(btn){btn.disabled=true;btn.textContent='…';}
|
||||
var startPromise;
|
||||
if(_storeFileId){
|
||||
startPromise=fetch(_apiUrl('/kx/print'),{
|
||||
method:'POST',
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({
|
||||
file_id:_storeFileId,
|
||||
filament_assignments:assignments,
|
||||
excluded_objects:excludedObjects,
|
||||
auto_leveling:fdAutoLeveling
|
||||
})
|
||||
post('/printer/print/start',{filename:S.file_ready,filament_assignments:assignments,excluded_objects:excludedObjects,auto_leveling:fdAutoLeveling})
|
||||
.then(function(r){return r.json();})
|
||||
.then(function(){
|
||||
document.getElementById('file-ready-banner').style.display='none';
|
||||
if(btn){btn.disabled=false;setText('file-ready-btn',T.file_ready_btn);}
|
||||
})
|
||||
.catch(function(e){
|
||||
clog(tr('log_error')+' '+e,'msg-err');
|
||||
if(btn){btn.disabled=false;setText('file-ready-btn',T.file_ready_btn);}
|
||||
});
|
||||
}else{
|
||||
startPromise=post('/printer/print/start',{
|
||||
filename:S.file_ready||_storeFilename,
|
||||
filament_assignments:assignments,
|
||||
excluded_objects:excludedObjects,
|
||||
auto_leveling:fdAutoLeveling
|
||||
});
|
||||
}
|
||||
startPromise.then(function(r){
|
||||
if(!r.ok){return r.text().then(function(t){throw new Error(t||('HTTP '+r.status));});}
|
||||
return r.json();
|
||||
}).then(function(d){
|
||||
if(d&&d.error) throw new Error(d.error);
|
||||
if(d&&d.result&&d.result!=='ok') throw new Error(String(d.result));
|
||||
document.getElementById('file-ready-banner').style.display='none';
|
||||
if(btn){btn.disabled=false;setText('file-ready-btn',T.file_ready_btn);}
|
||||
}).catch(function(e){
|
||||
clog(tr('log_error')+' '+e,'msg-err');
|
||||
if(btn){btn.disabled=false;setText('file-ready-btn',T.file_ready_btn);}
|
||||
});
|
||||
} else {
|
||||
// Store-Modus: POST /kx/print
|
||||
fetch(_apiUrl('/kx/print'),{
|
||||
@@ -2782,45 +2619,35 @@ function renderObjectSvg(){
|
||||
// ── Mid-Print Skip ──
|
||||
var _skipObjects=[]; // [{name, skipped, willSkip}]
|
||||
var _skipSvg='';
|
||||
var _skipPollTimer=null;
|
||||
function _applySkipDialogState(s){
|
||||
s=s||{};
|
||||
_skipSvg=s.svg_b64||'';
|
||||
var skipped=s.skipped||[];
|
||||
// Pending-Auswahl (willSkip) beim Refresh erhalten.
|
||||
var prevWillSkip={};
|
||||
(_skipObjects||[]).forEach(function(o){if(o&&o.name)prevWillSkip[o.name]=!!o.willSkip;});
|
||||
_skipObjects=(s.objects||[]).map(function(n){
|
||||
var isSkipped=(skipped.indexOf(n)>=0);
|
||||
return {name:n, skipped:isSkipped, willSkip:isSkipped?false:!!prevWillSkip[n]};
|
||||
});
|
||||
renderSkipList(); renderSkipSvg();
|
||||
}
|
||||
function openSkipDialog(){
|
||||
document.getElementById('skip-status').textContent='';
|
||||
document.getElementById('skip-confirm').disabled=false;
|
||||
_refreshSkipDialog();
|
||||
if(_skipPollTimer)clearInterval(_skipPollTimer);
|
||||
_skipPollTimer=setInterval(function(){
|
||||
var dlg=document.getElementById('skip-dialog');
|
||||
if(!(dlg&&dlg.classList.contains('open')))return;
|
||||
_refreshSkipDialog();
|
||||
},2000);
|
||||
document.getElementById('skip-dialog').classList.add('open');
|
||||
}
|
||||
function _refreshSkipDialog(){
|
||||
// query-Endpoint wartet intern auf frischen skip/report (bis 1.5s).
|
||||
fetch(_apiUrl('/kx/skip/query'),{method:'POST'})
|
||||
.then(function(r){return r.json();})
|
||||
.then(function(d){_applySkipDialogState(d.result||{});})
|
||||
.catch(function(){
|
||||
fetch(_apiUrl('/kx/skip/state')).then(function(r){return r.json();}).then(function(d){
|
||||
_applySkipDialogState(d.result||{});
|
||||
}).catch(function(){});
|
||||
// Erst aktueller State (mit DB-Objects + svg), dann query_obj für frischen skipped
|
||||
fetch(_apiUrl('/kx/skip/state')).then(function(r){return r.json()}).then(function(d){
|
||||
var s=d.result||{};
|
||||
_skipSvg=s.svg_b64||'';
|
||||
_skipObjects=(s.objects||[]).map(function(n){
|
||||
return {name:n, skipped:(s.skipped||[]).indexOf(n)>=0, willSkip:false};
|
||||
});
|
||||
renderSkipList(); renderSkipSvg();
|
||||
});
|
||||
// Frisch nachfragen (skipped-Liste aktualisieren)
|
||||
fetch(_apiUrl('/kx/skip/query'),{method:'POST'}).then(function(r){return r.json()}).then(function(){
|
||||
setTimeout(function(){
|
||||
fetch(_apiUrl('/kx/skip/state')).then(function(r){return r.json()}).then(function(d){
|
||||
var s=d.result||{};
|
||||
var skipped=s.skipped||[];
|
||||
_skipObjects.forEach(function(o){ o.skipped=skipped.indexOf(o.name)>=0; if(o.skipped)o.willSkip=false; });
|
||||
renderSkipList(); renderSkipSvg();
|
||||
});
|
||||
}, 500);
|
||||
}).catch(function(){});
|
||||
}
|
||||
function closeSkipDialog(){
|
||||
if(_skipPollTimer){clearInterval(_skipPollTimer);_skipPollTimer=null;}
|
||||
document.getElementById('skip-dialog').classList.remove('open');
|
||||
}
|
||||
function _shortLabel(name){
|
||||
|
||||
@@ -522,7 +522,7 @@
|
||||
<option value="en">English</option>
|
||||
<option value="es">Espanol</option>
|
||||
<option value="fr">Français</option>
|
||||
<option value="it">Italiano</option>
|
||||
<option value="it">Italiano</option>
|
||||
<option value="zh-cn">中文(简体)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user