diff --git a/web/themes/default/app.js b/web/themes/default/app.js index 0e702b1..d904495 100644 --- a/web/themes/default/app.js +++ b/web/themes/default/app.js @@ -1,1134 +1,1796 @@ // ── State ── -var S={nozzle_temp:0,nozzle_target:0,bed_temp:0,bed_target:0, - print_state:'standby',filename:'',progress:0,print_duration:0,remain_time:0, - curr_layer:0,total_layers:0,z_mm:0,printer_name:'Kobra X',firmware_version:'–', - camera_url:'',fan_speed:0,print_speed_mode:2,light_on:false,light_brightness:80, - ams_slots:[],filament_mode:'toolhead',ace_units:[],ace_dry_presets:null,ace_drying:{status:0,target_temp:0,duration:0,remain_time:0,humidity:null,current_temp:null,units:[]},web_upload_warning:1}; -var tempHistory={n:[],b:[]}; -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 currentStep=1; -var currentPanel='dashboard'; -var aceAutoRefillPrefs=(function(){ - try{return JSON.parse(localStorage.getItem('aceAutoRefillPrefs')||'{}')||{};}catch(_){return {};} -})(); -var aceDryProfiles=(function(){ - try{return JSON.parse(localStorage.getItem('aceDryProfiles')||'{}')||{};}catch(_){return {};} -})(); -var _aceDryDialogAceId=-1; -var _aceDryDialogPresetKey=''; -var _aceDryDialogPresetOriginals={}; -var ACE_DRY_PRESET_DEFAULTS={ - pla:{temp:45,duration_sec:4*3600}, - pla_plus:{temp:45,duration_sec:4*3600}, - petg:{temp:50,duration_sec:4*3600}, - tpu:{temp:55,duration_sec:4*3600}, - abs_asa:{temp:45,duration_sec:8*3600}, - pa_pc:{temp:55,duration_sec:12*3600} +var S = { + nozzle_temp: 0, + nozzle_target: 0, + bed_temp: 0, + bed_target: 0, + print_state: "standby", + filename: "", + progress: 0, + print_duration: 0, + remain_time: 0, + curr_layer: 0, + total_layers: 0, + z_mm: 0, + printer_name: "Kobra X", + firmware_version: "–", + camera_url: "", + fan_speed: 0, + print_speed_mode: 2, + light_on: false, + light_brightness: 80, + ams_slots: [], + filament_mode: "toolhead", + ace_units: [], + ace_dry_presets: null, + ace_drying: { + status: 0, + target_temp: 0, + duration: 0, + remain_time: 0, + humidity: null, + current_temp: null, + units: [], + }, + web_upload_warning: 1, }; -var ACE_DRY_PRESETS={ - pla:{temp:45,duration_sec:4*3600}, - pla_plus:{temp:45,duration_sec:4*3600}, - petg:{temp:50,duration_sec:4*3600}, - tpu:{temp:55,duration_sec:4*3600}, - abs_asa:{temp:45,duration_sec:8*3600}, - pa_pc:{temp:55,duration_sec:12*3600}, - custom_1:{name:'Custom 1',temp:45,duration_sec:4*3600}, - custom_2:{name:'Custom 2',temp:45,duration_sec:4*3600}, - custom_3:{name:'Custom 3',temp:45,duration_sec:4*3600} +var tempHistory = { n: [], b: [] }; +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 currentStep = 1; +var currentPanel = "dashboard"; +var aceAutoRefillPrefs = (function () { + try { + return JSON.parse(localStorage.getItem("aceAutoRefillPrefs") || "{}") || {}; + } catch (_) { + return {}; + } +})(); +var aceDryProfiles = (function () { + try { + return JSON.parse(localStorage.getItem("aceDryProfiles") || "{}") || {}; + } catch (_) { + return {}; + } +})(); +var _aceDryDialogAceId = -1; +var _aceDryDialogPresetKey = ""; +var _aceDryDialogPresetOriginals = {}; +var ACE_DRY_PRESET_DEFAULTS = { + pla: { temp: 45, duration_sec: 4 * 3600 }, + pla_plus: { temp: 45, duration_sec: 4 * 3600 }, + petg: { temp: 50, duration_sec: 4 * 3600 }, + tpu: { temp: 55, duration_sec: 4 * 3600 }, + abs_asa: { temp: 45, duration_sec: 8 * 3600 }, + pa_pc: { temp: 55, duration_sec: 12 * 3600 }, +}; +var ACE_DRY_PRESETS = { + pla: { temp: 45, duration_sec: 4 * 3600 }, + pla_plus: { temp: 45, duration_sec: 4 * 3600 }, + petg: { temp: 50, duration_sec: 4 * 3600 }, + tpu: { temp: 55, duration_sec: 4 * 3600 }, + abs_asa: { temp: 45, duration_sec: 8 * 3600 }, + pa_pc: { temp: 55, duration_sec: 12 * 3600 }, + custom_1: { name: "Custom 1", temp: 45, duration_sec: 4 * 3600 }, + custom_2: { name: "Custom 2", temp: 45, duration_sec: 4 * 3600 }, + custom_3: { name: "Custom 3", temp: 45, duration_sec: 4 * 3600 }, }; -function _aceAutoRefillGet(aceId){return !!aceAutoRefillPrefs[String(aceId)];} -function _aceAutoRefillSet(aceId,on){ - aceAutoRefillPrefs[String(aceId)]=!!on; - localStorage.setItem('aceAutoRefillPrefs',JSON.stringify(aceAutoRefillPrefs)); +function _aceAutoRefillGet(aceId) { + return !!aceAutoRefillPrefs[String(aceId)]; } -function _aceDryProfileGet(aceId){ - var p=aceDryProfiles[String(aceId)]||{}; - var temp=parseInt(p.temp,10); - var dur=parseInt(p.duration_sec,10); - if(!Number.isFinite(temp))temp=45; - if(!Number.isFinite(dur))dur=4*3600; - temp=Math.max(30,Math.min(80,temp)); - dur=Math.max(10*60,Math.min(24*3600,dur)); - return {temp:temp,duration_sec:dur,preset:p.preset||''}; +function _aceAutoRefillSet(aceId, on) { + aceAutoRefillPrefs[String(aceId)] = !!on; + localStorage.setItem( + "aceAutoRefillPrefs", + JSON.stringify(aceAutoRefillPrefs), + ); } -function _aceDryProfileSet(aceId,temp,durationSec,preset){ - aceDryProfiles[String(aceId)]={ - temp:Math.max(30,Math.min(80,parseInt(temp,10)||45)), - duration_sec:Math.max(10*60,Math.min(24*3600,parseInt(durationSec,10)||4*3600)), - preset:preset||'' +function _aceDryProfileGet(aceId) { + var p = aceDryProfiles[String(aceId)] || {}; + var temp = parseInt(p.temp, 10); + var dur = parseInt(p.duration_sec, 10); + if (!Number.isFinite(temp)) temp = 45; + if (!Number.isFinite(dur)) dur = 4 * 3600; + temp = Math.max(30, Math.min(80, temp)); + dur = Math.max(10 * 60, Math.min(24 * 3600, dur)); + return { temp: temp, duration_sec: dur, preset: p.preset || "" }; +} +function _aceDryProfileSet(aceId, temp, durationSec, preset) { + aceDryProfiles[String(aceId)] = { + temp: Math.max(30, Math.min(80, parseInt(temp, 10) || 45)), + duration_sec: Math.max( + 10 * 60, + Math.min(24 * 3600, parseInt(durationSec, 10) || 4 * 3600), + ), + preset: preset || "", }; - localStorage.setItem('aceDryProfiles',JSON.stringify(aceDryProfiles)); + localStorage.setItem("aceDryProfiles", JSON.stringify(aceDryProfiles)); } -function _aceDryDurationMinFromSec(sec){ - var minutes=Math.round((parseInt(sec,10)||0)/60); - return Math.max(10,Math.min(1440,minutes)); +function _aceDryDurationMinFromSec(sec) { + var minutes = Math.round((parseInt(sec, 10) || 0) / 60); + return Math.max(10, Math.min(1440, minutes)); } -function _syncAceDryPresetsFromServer(raw){ - if(!raw||typeof raw!=='object')return; - Object.keys(ACE_DRY_PRESETS).forEach(function(k){ - var p=raw[k]; - if(!p||typeof p!=='object')return; - var t=parseInt(p.temp,10); - var d=parseInt(p.duration_sec,10); - if(Number.isFinite(t))ACE_DRY_PRESETS[k].temp=Math.max(30,Math.min(80,t)); - if(Number.isFinite(d))ACE_DRY_PRESETS[k].duration_sec=Math.max(10*60,Math.min(24*3600,d)); - if(/^custom_[123]$/.test(k)&&typeof p.name==='string'){ - var n=p.name.trim(); - ACE_DRY_PRESETS[k].name=n||('Custom '+k.slice(-1)); +function _syncAceDryPresetsFromServer(raw) { + if (!raw || typeof raw !== "object") return; + Object.keys(ACE_DRY_PRESETS).forEach(function (k) { + var p = raw[k]; + if (!p || typeof p !== "object") return; + var t = parseInt(p.temp, 10); + var d = parseInt(p.duration_sec, 10); + if (Number.isFinite(t)) + ACE_DRY_PRESETS[k].temp = Math.max(30, Math.min(80, t)); + if (Number.isFinite(d)) + ACE_DRY_PRESETS[k].duration_sec = Math.max( + 10 * 60, + Math.min(24 * 3600, d), + ); + if (/^custom_[123]$/.test(k) && typeof p.name === "string") { + var n = p.name.trim(); + ACE_DRY_PRESETS[k].name = n || "Custom " + k.slice(-1); } }); } // ── Theme ── -function toggleTheme(){ - var h=document.documentElement; - h.setAttribute('data-theme',h.getAttribute('data-theme')==='dark'?'light':'dark'); - localStorage.setItem('theme',h.getAttribute('data-theme')); +function toggleTheme() { + var h = document.documentElement; + h.setAttribute( + "data-theme", + h.getAttribute("data-theme") === "dark" ? "light" : "dark", + ); + localStorage.setItem("theme", h.getAttribute("data-theme")); } -(function(){var t=localStorage.getItem('theme');if(t)document.documentElement.setAttribute('data-theme',t)})(); +(function () { + var t = localStorage.getItem("theme"); + if (t) document.documentElement.setAttribute("data-theme", t); +})(); // ── i18n ── -var currentLang='de'; -var T={}; -var _langCache={}; +var currentLang = "de"; +var T = {}; +var _langCache = {}; -function tr(key,fallback){ - var v=T&&T[key]; - return (typeof v==='string'&&v.length)?v:(fallback!==undefined?fallback:''); +function tr(key, fallback) { + var v = T && T[key]; + return typeof v === "string" && v.length + ? v + : fallback !== undefined + ? fallback + : ""; } -function _langToggleLabel(lang){ - if(lang==='de')return 'Deutsch'; - if(lang==='en')return 'English'; - if(lang==='fr')return 'Français'; - if(lang==='it')return 'Italiano'; - if(lang==='zh-cn')return '简体中文'; - return 'Espanol'; +function _langToggleLabel(lang) { + if (lang === "de") return "Deutsch"; + if (lang === "en") return "English"; + if (lang === "fr") return "Français"; + if (lang === "it") return "Italiano"; + if (lang === "zh-cn") return "简体中文"; + return "Espanol"; } -function _mapSupportedLang(lang){ - if(!lang)return ''; - var l=String(lang).toLowerCase().replace(/_/g,'-').trim(); - if(l==='de'||l==='en'||l==='es'||l==='fr'||l==='it'||l==='zh-cn')return l; +function _mapSupportedLang(lang) { + if (!lang) return ""; + var l = String(lang).toLowerCase().replace(/_/g, "-").trim(); + if ( + l === "de" || + l === "en" || + l === "es" || + l === "fr" || + l === "it" || + l === "zh-cn" + ) + return l; - var base=l.split('-')[0]; - if(base==='de'||base==='en'||base==='es'||base==='fr'||base==='it')return base; + var base = l.split("-")[0]; + if ( + base === "de" || + base === "en" || + base === "es" || + base === "fr" || + base === "it" + ) + return base; - if(base==='zh'){ - if(l.indexOf('cn')>=0||l.indexOf('hans')>=0||l==='zh')return 'zh-cn'; + if (base === "zh") { + if (l.indexOf("cn") >= 0 || l.indexOf("hans") >= 0 || l === "zh") + return "zh-cn"; } - return ''; + return ""; } -function _normalizeLang(lang){ - return _mapSupportedLang(lang)||'de'; +function _normalizeLang(lang) { + return _mapSupportedLang(lang) || "de"; } -function _detectBrowserLanguage(){ - var prefs=[]; - if(Array.isArray(navigator.languages)&&navigator.languages.length)prefs=navigator.languages; - else if(navigator.language)prefs=[navigator.language]; - for(var i=0;i1){ - if(wrap)wrap.style.display=''; - if(single)single.style.display='none'; - var menu=document.getElementById('printer-dropdown-menu'); - if(menu){ - menu.innerHTML=_printers.map(function(p){ - var active=_activePrinter&&String(p.id)===String(_activePrinter.id); - var num=p.id; - return ''+ - (active?'▶ ':'')+p.name+''; - }).join(''); +function renderPrinterDropdown() { + var wrap = document.getElementById("printer-dropdown-wrap"); + var single = document.getElementById("h-pname-single"); + var name = + _printers.length === 0 + ? "–" + : _activePrinter + ? _activePrinter.name || "Kobra X" + : "Kobra X"; + var pname = document.getElementById("h-pname"); + if (pname) pname.textContent = name; + if (single) single.textContent = name; + if (_printers.length > 1) { + if (wrap) wrap.style.display = ""; + if (single) single.style.display = "none"; + var menu = document.getElementById("printer-dropdown-menu"); + if (menu) { + menu.innerHTML = _printers + .map(function (p) { + var active = + _activePrinter && String(p.id) === String(_activePrinter.id); + var num = p.id; + return ( + '" + + (active ? "▶ " : "") + + p.name + + "" + ); + }) + .join(""); } } else { - if(wrap)wrap.style.display='none'; - if(single)single.style.display=''; + if (wrap) wrap.style.display = "none"; + if (single) single.style.display = ""; } } -function togglePrinterDropdown(){ - var menu=document.getElementById('printer-dropdown-menu'); - if(menu)menu.style.display=menu.style.display==='none'?'block':'none'; +function togglePrinterDropdown() { + var menu = document.getElementById("printer-dropdown-menu"); + if (menu) + menu.style.display = menu.style.display === "none" ? "block" : "none"; } -document.addEventListener('click',function(e){ - var wrap=document.getElementById('printer-dropdown-wrap'); - if(wrap&&!wrap.contains(e.target)){ - var menu=document.getElementById('printer-dropdown-menu'); - if(menu)menu.style.display='none'; +document.addEventListener("click", function (e) { + var wrap = document.getElementById("printer-dropdown-wrap"); + if (wrap && !wrap.contains(e.target)) { + var menu = document.getElementById("printer-dropdown-menu"); + if (menu) menu.style.display = "none"; } }); -function applyLang(){ +function applyLang() { ensureAceDryCards(); // Nav - var nb=document.getElementById('nb-dashboard');if(nb)nb.querySelector('.nav-text').textContent=T.nav_dashboard; - nb=document.getElementById('nb-console');if(nb)nb.querySelector('.nav-text').textContent=T.nav_console; - nb=document.getElementById('nb-printers');if(nb)nb.querySelector('.nav-text').textContent=T.nav_printers; - nb=document.getElementById('nb-store');if(nb)nb.querySelector('.nav-text').textContent=T.nav_browser; + var nb = document.getElementById("nb-dashboard"); + if (nb) nb.querySelector(".nav-text").textContent = T.nav_dashboard; + nb = document.getElementById("nb-console"); + if (nb) nb.querySelector(".nav-text").textContent = T.nav_console; + nb = document.getElementById("nb-printers"); + if (nb) nb.querySelector(".nav-text").textContent = T.nav_printers; + nb = document.getElementById("nb-store"); + if (nb) nb.querySelector(".nav-text").textContent = T.nav_browser; // Bottom nav - var bnb=document.getElementById('bnb-dashboard');if(bnb)bnb.lastChild.textContent=T.nav_dashboard; - bnb=document.getElementById('bnb-console');if(bnb)bnb.lastChild.textContent=T.nav_console; - bnb=document.getElementById('bnb-printers');if(bnb)bnb.lastChild.textContent=T.nav_printers; - bnb=document.getElementById('bnb-store');if(bnb)bnb.lastChild.textContent=T.nav_browser; + var bnb = document.getElementById("bnb-dashboard"); + if (bnb) bnb.lastChild.textContent = T.nav_dashboard; + bnb = document.getElementById("bnb-console"); + if (bnb) bnb.lastChild.textContent = T.nav_console; + bnb = document.getElementById("bnb-printers"); + if (bnb) bnb.lastChild.textContent = T.nav_printers; + bnb = document.getElementById("bnb-store"); + if (bnb) bnb.lastChild.textContent = T.nav_browser; // Browser panel - setText('printers-panel-title','🖨 '+T.nav_printers); - setText('add-printer-btn-label',T.add_printer); - setText('apd-title',T.add_printer); - setText('skip-title',T.skip_title); - setText('skip-hint',T.skip_hint); - setText('d-btn-skip-label',T.skip_btn_label); - setText('fd-objects-toggle-lbl',T.fd_objects_toggle); - setText('apd-lbl-ip',T.apd_lbl_ip); - setText('apd-lbl-name',T.apd_lbl_name); - var apn=document.getElementById('apd-name');if(apn)apn.setAttribute('placeholder',T.apd_placeholder_name); - setText('apd-cancel',T.apd_cancel); - setText('apd-confirm',T.apd_confirm); - setText('fd-slots-hint',T.fd_slots_hint); - setText('fd-cancel',T.fd_cancel); - setText('fd-print',T.fd_print); - setText('store-panel-title','🗂 '+T.panel_browser_title); - var srb=document.getElementById('store-refresh-btn');if(srb)srb.textContent=T.store_refresh; - var ssp=document.getElementById('store-search');if(ssp)ssp.setAttribute('placeholder',T.store_search_placeholder); - setText('store-upload-label-prefix',T.store_upload_label_prefix); - setText('store-upload-label-browse',T.store_upload_label_browse); - setText('store-empty',T.store_empty); - setText('sf-all',T.sf_all);setText('sf-ok',T.sf_ok);setText('sf-err',T.sf_err);setText('sf-new',T.sf_new); - setText('ss-date',T.ss_date);setText('ss-name',T.ss_name);setText('ss-dur',T.ss_dur); - setText('store-web-verify-title',T.store_web_verify_title); - setText('store-web-verify-msg',T.store_web_verify_msg); - setText('store-web-verify-confirm',T.store_web_verify_confirm); - setText('store-web-verify-abort',T.store_web_verify_abort); + setText("printers-panel-title", "🖨 " + T.nav_printers); + setText("add-printer-btn-label", T.add_printer); + setText("apd-title", T.add_printer); + setText("skip-title", T.skip_title); + setText("skip-hint", T.skip_hint); + setText("d-btn-skip-label", T.skip_btn_label); + setText("fd-objects-toggle-lbl", T.fd_objects_toggle); + setText("apd-lbl-ip", T.apd_lbl_ip); + setText("apd-lbl-name", T.apd_lbl_name); + var apn = document.getElementById("apd-name"); + if (apn) apn.setAttribute("placeholder", T.apd_placeholder_name); + setText("apd-cancel", T.apd_cancel); + setText("apd-confirm", T.apd_confirm); + setText("fd-slots-hint", T.fd_slots_hint); + setText("fd-cancel", T.fd_cancel); + setText("fd-print", T.fd_print); + setText("store-panel-title", "🗂 " + T.panel_browser_title); + var srb = document.getElementById("store-refresh-btn"); + if (srb) srb.textContent = T.store_refresh; + var ssp = document.getElementById("store-search"); + if (ssp) ssp.setAttribute("placeholder", T.store_search_placeholder); + setText("store-upload-label-prefix", T.store_upload_label_prefix); + setText("store-upload-label-browse", T.store_upload_label_browse); + setText("store-empty", T.store_empty); + setText("sf-all", T.sf_all); + setText("sf-ok", T.sf_ok); + setText("sf-err", T.sf_err); + setText("sf-new", T.sf_new); + setText("ss-date", T.ss_date); + setText("ss-name", T.ss_name); + setText("ss-dur", T.ss_dur); + setText("store-web-verify-title", T.store_web_verify_title); + setText("store-web-verify-msg", T.store_web_verify_msg); + setText("store-web-verify-confirm", T.store_web_verify_confirm); + setText("store-web-verify-abort", T.store_web_verify_abort); // Dashboard card titles - setText('d-card-progress',T.card_progress); - setText('d-card-temps',T.card_temps); - setText('d-card-lightfan',T.card_light_fan); - setText('d-card-speed',T.card_speed); - setText('d-card-cam',T.card_cam); - setText('d-card-ams',T.panel_ams_title); - setText('d-lbl-elapsed',T.lbl_elapsed); - setText('d-lbl-remain',T.lbl_remaining); - setText('d-slicer-label',T.lbl_slicer_time); - setText('d-lbl-layers',T.lbl_layers); - setText('d-lbl-zpos',T.lbl_zpos); - setText('d-lbl-light',T.lbl_light); - setText('d-lbl-nozzle',T.label_nozzle); - setText('d-lbl-bed',T.label_bed); + setText("d-card-progress", T.card_progress); + setText("d-card-temps", T.card_temps); + setText("d-card-lightfan", T.card_light_fan); + setText("d-card-speed", T.card_speed); + setText("d-card-cam", T.card_cam); + setText("d-card-ams", T.panel_ams_title); + setText("d-lbl-elapsed", T.lbl_elapsed); + setText("d-lbl-remain", T.lbl_remaining); + setText("d-slicer-label", T.lbl_slicer_time); + setText("d-lbl-layers", T.lbl_layers); + setText("d-lbl-zpos", T.lbl_zpos); + setText("d-lbl-light", T.lbl_light); + setText("d-lbl-nozzle", T.label_nozzle); + setText("d-lbl-bed", T.label_bed); // Dashboard buttons — Pause-Button wird zur Toggle-Action; Resume-Beschriftung // wird in updatePauseResumeBtn() je nach Druckerzustand gesetzt. updatePauseResumeBtn(); - setText('d-btn-cancel',T.btn_cancel); - setText('cam-toggle-btn',camOn?T.btn_cam_stop:T.btn_cam_start); - setText('cam-placeholder-txt',T.cam_placeholder); + setText("d-btn-cancel", T.btn_cancel); + setText("cam-toggle-btn", camOn ? T.btn_cam_stop : T.btn_cam_start); + setText("cam-placeholder-txt", T.cam_placeholder); // Temp labels - document.querySelectorAll('.lbl-set').forEach(e=>e.textContent=T.label_set); - document.querySelectorAll('.lbl-off').forEach(e=>e.textContent=T.label_off); - setText('d-chart-label',T.panel_temps_chart); + document + .querySelectorAll(".lbl-set") + .forEach((e) => (e.textContent = T.label_set)); + document + .querySelectorAll(".lbl-off") + .forEach((e) => (e.textContent = T.label_off)); + setText("d-chart-label", T.panel_temps_chart); // Axis labels - setText('ptitle-motion-xy',T.panel_motion_xy); - setText('ptitle-motion-z',T.panel_motion_z); - document.querySelectorAll('.lbl-home-z').forEach(e=>e.textContent=T.btn_home_z); - document.querySelectorAll('.lbl-home-xy').forEach(e=>e.textContent=T.btn_home_xy); - document.querySelectorAll('.lbl-home-all').forEach(e=>e.textContent=T.btn_home_all); - document.querySelectorAll('.lbl-disable-motors').forEach(e=>e.textContent=T.btn_disable_motors); - document.querySelectorAll('.lbl-step').forEach(e=>e.textContent=T.label_step); - document.querySelectorAll('.temp-input').forEach(e=>e.setAttribute('placeholder',T.label_target_c.replace(':',''))); + setText("ptitle-motion-xy", T.panel_motion_xy); + setText("ptitle-motion-z", T.panel_motion_z); + document + .querySelectorAll(".lbl-home-z") + .forEach((e) => (e.textContent = T.btn_home_z)); + document + .querySelectorAll(".lbl-home-xy") + .forEach((e) => (e.textContent = T.btn_home_xy)); + document + .querySelectorAll(".lbl-home-all") + .forEach((e) => (e.textContent = T.btn_home_all)); + document + .querySelectorAll(".lbl-disable-motors") + .forEach((e) => (e.textContent = T.btn_disable_motors)); + document + .querySelectorAll(".lbl-step") + .forEach((e) => (e.textContent = T.label_step)); + document + .querySelectorAll(".temp-input") + .forEach((e) => + e.setAttribute("placeholder", T.label_target_c.replace(":", "")), + ); // Console - setText('ptitle-console',T.panel_console_title); + setText("ptitle-console", T.panel_console_title); // Settings-Panel - setText('modal-sec-connection',T.settings_connection); - setText('modal-sec-print',T.settings_print); - setText('modal-sec-notifications',T.settings_notifications); - var nhEl=document.getElementById('notif-hint');if(nhEl && T.settings_notifications_hint)nhEl.innerHTML=T.settings_notifications_hint; - setText('lbl-notif-add',T.settings_notif_add); - setText('lbl-notif-interval',T.settings_notif_interval_lbl); - setText('lbl-notif-min-unit',T.settings_notif_min_unit); - setText('lbl-notif-layers-unit',T.settings_notif_layers_unit); - setText('lbl-notif-zero-off',T.settings_notif_zero_off); - setText('modal-sec-version',T.settings_version); + setText("modal-sec-connection", T.settings_connection); + setText("modal-sec-print", T.settings_print); + setText("modal-sec-notifications", T.settings_notifications); + var nhEl = document.getElementById("notif-hint"); + if (nhEl && T.settings_notifications_hint) + nhEl.innerHTML = T.settings_notifications_hint; + setText("lbl-notif-add", T.settings_notif_add); + setText("lbl-notif-interval", T.settings_notif_interval_lbl); + setText("lbl-notif-min-unit", T.settings_notif_min_unit); + setText("lbl-notif-layers-unit", T.settings_notif_layers_unit); + setText("lbl-notif-zero-off", T.settings_notif_zero_off); + setText("modal-sec-version", T.settings_version); // Nav + Kategorie-Labels (mit Fallback falls i18n-Key noch fehlt) - setText('nav-settings',T.nav_settings||'Einstellungen'); - setText('setcat-lbl-connection',T.settings_connection||'Verbindung'); - setText('setcat-lbl-printer',T.settings_print||'Drucker'); - setText('setcat-lbl-display',T.settings_cat_display||'Darstellung'); - setText('setcat-lbl-display2',T.settings_cat_display||'Darstellung'); - setText('setcat-lbl-filament',T.settings_cat_filament||'Filament'); - setText('setcat-lbl-system',T.settings_version||'System'); - setText('lbl-set-lang',T.settings_cat_language||'Sprache'); - setText('lbl-set-theme',T.settings_cat_theme||'Hell / Dunkel umschalten'); - setText('lbl-poll-interval',T.settings_poll||'Poll-Intervall (Sekunden)'); - setText('lbl-filament-mapping',T.settings_filament_mapping||'Filament-Profil-Mapping (pro Slot)'); - setText('lbl-filament-mapping-save',T.settings_filament_mapping_save||'Mapping speichern'); - setText('lbl-visible-vendors',T.settings_visible_vendors||'Sichtbare Hersteller (Profil-Dropdown)'); - setText('visible-vendors-hint',T.settings_visible_vendors_hint||'Nur diese Hersteller erscheinen im Slot-Profil-Dropdown. Nichts ausgewählt = alle anzeigen. „Generic" und eigene Profile sind immer sichtbar.'); - setText('lbl-visible-vendors-save',T.settings_visible_vendors_save||'Auswahl speichern'); + setText("nav-settings", T.nav_settings || "Einstellungen"); + setText("setcat-lbl-connection", T.settings_connection || "Verbindung"); + setText("setcat-lbl-printer", T.settings_print || "Drucker"); + setText("setcat-lbl-display", T.settings_cat_display || "Darstellung"); + setText("setcat-lbl-display2", T.settings_cat_display || "Darstellung"); + setText("setcat-lbl-filament", T.settings_cat_filament || "Filament"); + setText("setcat-lbl-system", T.settings_version || "System"); + setText("lbl-set-lang", T.settings_cat_language || "Sprache"); + setText("lbl-set-theme", T.settings_cat_theme || "Hell / Dunkel umschalten"); + setText("lbl-poll-interval", T.settings_poll || "Poll-Intervall (Sekunden)"); + var pollHint = document.getElementById("lbl-poll-hint"); + if (pollHint) + pollHint.textContent = + T.settings_poll_interval_hint || + "Wie oft die Bridge den Drucker-Status abfragt"; + setText( + "lbl-filament-mapping", + T.settings_filament_mapping || "Filament-Profil-Mapping (pro Slot)", + ); + var fmHint = document.getElementById("filament-mapping-hint"); + if (fmHint) + fmHint.textContent = + T.settings_filament_mapping_hint || + 'Festes Orca-Profil pro AMS-Slot. Beim Slicer-Sync sendet die Bridge dieses Profil statt „Generic".'; + setText( + "lbl-filament-mapping-save", + T.settings_filament_mapping_save || "Mapping speichern", + ); + setText( + "lbl-visible-vendors", + T.settings_visible_vendors || "Sichtbare Hersteller (Profil-Dropdown)", + ); + var vfs = document.getElementById("vendor-filter-search"); + if (vfs) + vfs.setAttribute( + "placeholder", + T.settings_vendor_filter_placeholder || "Hersteller suchen…", + ); + setText( + "visible-vendors-hint", + T.settings_visible_vendors_hint || + 'Nur diese Hersteller erscheinen im Slot-Profil-Dropdown. Nichts ausgewählt = alle anzeigen. „Generic" und eigene Profile sind immer sichtbar.', + ); + setText( + "lbl-visible-vendors-save", + T.settings_visible_vendors_save || "Auswahl speichern", + ); // Custom-Profile-Import (Issue #41) - setText('modal-sec-orca-profiles',T.orca_profile_section); - setText('orca-profiles-hint',T.orca_profile_hint); - setText('lbl-orca-profiles-import',T.orca_profile_import_btn); - setText('lbl-slot-profile-import',T.orca_profile_import_link); - setText('profile-import-title',T.orca_profile_import_title); - setText('profile-import-dropmsg',T.orca_profile_dropmsg); - setText('profile-import-list-label',T.orca_profile_list_label); + setText("modal-sec-orca-profiles", T.orca_profile_section); + setText("orca-profiles-hint", T.orca_profile_hint); + setText("lbl-orca-profiles-import", T.orca_profile_import_btn); + setText("lbl-slot-profile-import", T.orca_profile_import_link); + setText("profile-import-title", T.orca_profile_import_title); + setText("profile-import-dropmsg", T.orca_profile_dropmsg); + setText("profile-import-list-label", T.orca_profile_list_label); // Hilfe-Text mit Inline-HTML — innerHTML statt setText - var helpEl=document.getElementById('profile-import-help'); - if(helpEl && T.orca_profile_help_html) helpEl.innerHTML=T.orca_profile_help_html; - setText('btn-save-settings',T.settings_save); - setText('lbl-printer-name',T.settings_printer_name); - setText('lbl-printer-ip',T.settings_printer_ip); - setText('lbl-mqtt-port',T.settings_mqtt_port); - setText('lbl-username',T.settings_username); - setText('lbl-password',T.settings_password); - setText('lbl-device-id',T.settings_device_id); - setText('lbl-mode-id',T.settings_mode_id); - setText('lbl-default-slot',T.settings_default_slot); - setText('opt-slot-auto',T.settings_slot_auto); - setText('lbl-auto-leveling',T.settings_auto_leveling); - setText('lbl-file-ready-mode',T.settings_file_ready_mode); - setText('opt-file-ready-dialog',T.settings_file_ready_dialog); - setText('opt-file-ready-banner',T.settings_file_ready_banner); - setText('lbl-camera-on-print',T.settings_camera_on_print); - setText('lbl-web-upload-warning',T.settings_web_upload_warning); - setText('fd-options-title',T.fd_options_title); - setText('fd-lbl-auto-leveling',T.print_auto_leveling); + var helpEl = document.getElementById("profile-import-help"); + if (helpEl && T.orca_profile_help_html) + helpEl.innerHTML = T.orca_profile_help_html; + setText("btn-save-settings", T.settings_save); + setText("lbl-printer-name", T.settings_printer_name); + setText("lbl-printer-ip", T.settings_printer_ip); + setText("lbl-mqtt-port", T.settings_mqtt_port); + setText("lbl-username", T.settings_username); + setText("lbl-password", T.settings_password); + setText("lbl-device-id", T.settings_device_id); + setText("lbl-mode-id", T.settings_mode_id); + setText("lbl-default-slot", T.settings_default_slot); + setText("opt-slot-auto", T.settings_slot_auto); + setText("lbl-auto-leveling", T.settings_auto_leveling); + setText("lbl-file-ready-mode", T.settings_file_ready_mode); + setText("opt-file-ready-dialog", T.settings_file_ready_dialog); + setText("opt-file-ready-banner", T.settings_file_ready_banner); + setText("lbl-camera-on-print", T.settings_camera_on_print); + setText("lbl-web-upload-warning", T.settings_web_upload_warning); + setText("fd-options-title", T.fd_options_title); + setText("fd-lbl-auto-leveling", T.print_auto_leveling); - setText('lbl-update-check',T.update_check); - setText('lbl-update-apply',T.update_apply); + setText("lbl-update-check", T.update_check); + setText("lbl-update-apply", T.update_apply); // Progress-Karten-Aktionen für geladene/idle Datei (Issue #55) - setText('d-idle-print-lbl',T.progress_action_print||'Drucken'); - setText('d-idle-slots-lbl',T.progress_action_slots||'Slots zuordnen'); - setText('d-idle-clear-lbl',T.progress_action_clear||'Leeren'); + setText("d-idle-print-lbl", T.progress_action_print || "Drucken"); + setText("d-idle-slots-lbl", T.progress_action_slots || "Slots zuordnen"); + setText("d-idle-clear-lbl", T.progress_action_clear || "Leeren"); // Speed buttons - setText('d-spd-lbl-1',T.speed_silent.replace(/^\S+\s/,'')); - setText('d-spd-lbl-2',T.speed_normal.replace(/^\S+\s/,'')); - setText('d-spd-lbl-3',T.speed_sport.replace(/^\S+\s/,'')); + setText("d-spd-lbl-1", T.speed_silent.replace(/^\S+\s/, "")); + setText("d-spd-lbl-2", T.speed_normal.replace(/^\S+\s/, "")); + setText("d-spd-lbl-3", T.speed_sport.replace(/^\S+\s/, "")); // AMS feed/unload - document.querySelectorAll('.lbl-feed').forEach(e=>e.textContent=T.lbl_feed); - document.querySelectorAll('.lbl-unload').forEach(e=>e.textContent=T.lbl_unload); - for(var i=0;i<4;i++){ - setText('d-card-ace-dry-'+i,'ACE '+(i+1)+' - '+tr('ace_dry_dryer')); - setText('d-ace-auto-refill-label-'+i,tr('ace_dry_auto_refill')); - setText('d-ace-drying-enable-label-'+i,tr('ace_dry_enable')); - setText('d-ace-dry-humidity-label-'+i,tr('ace_dry_humidity')+':'); - setText('d-ace-dry-current-temp-label-'+i,tr('ace_dry_current_temp')+':'); - setText('d-ace-dry-target-label-'+i,tr('ace_dry_temp_line')+':'); - setText('d-ace-dry-time-label-'+i,tr('ace_dry_time_line')+':'); - setText('d-ace-dry-chart-label-'+i,tr('ace_dry_chart')); - var adTemp=document.getElementById('ace-dry-temp-'+i);if(adTemp)adTemp.setAttribute('placeholder',T.ace_dry_temp); - var adDur=document.getElementById('ace-dry-duration-'+i);if(adDur)adDur.setAttribute('placeholder',T.ace_dry_duration); + document + .querySelectorAll(".lbl-feed") + .forEach((e) => (e.textContent = T.lbl_feed)); + document + .querySelectorAll(".lbl-unload") + .forEach((e) => (e.textContent = T.lbl_unload)); + for (var i = 0; i < 4; i++) { + setText( + "d-card-ace-dry-" + i, + "ACE " + (i + 1) + " - " + tr("ace_dry_dryer"), + ); + setText("d-ace-auto-refill-label-" + i, tr("ace_dry_auto_refill")); + setText("d-ace-drying-enable-label-" + i, tr("ace_dry_enable")); + setText("d-ace-dry-humidity-label-" + i, tr("ace_dry_humidity") + ":"); + setText( + "d-ace-dry-current-temp-label-" + i, + tr("ace_dry_current_temp") + ":", + ); + setText("d-ace-dry-target-label-" + i, tr("ace_dry_temp_line") + ":"); + setText("d-ace-dry-time-label-" + i, tr("ace_dry_time_line") + ":"); + setText("d-ace-dry-chart-label-" + i, tr("ace_dry_chart")); + var adTemp = document.getElementById("ace-dry-temp-" + i); + if (adTemp) adTemp.setAttribute("placeholder", T.ace_dry_temp); + var adDur = document.getElementById("ace-dry-duration-" + i); + if (adDur) adDur.setAttribute("placeholder", T.ace_dry_duration); } - setText('ace-dry-dialog-title',tr('ace_dry_dialog_title')); - setText('ace-dry-dialog-temp-label',tr('ace_dry_dialog_temp')); - setText('ace-dry-dialog-time-label',tr('ace_dry_dialog_time')); - setText('ace-dry-dialog-custom-name-label',tr('ace_dry_dialog_custom_name')); - setText('ace-dry-dialog-cancel',tr('ace_dry_dialog_cancel')); - setText('ace-dry-dialog-confirm',tr('ace_dry_dialog_confirm')); - setText('ace-dry-dialog-reset-default',tr('ace_dry_dialog_reset_default')); - setText('ace-dry-dialog-save-preset',tr('ace_dry_dialog_save_restart')); + setText("ace-dry-dialog-title", tr("ace_dry_dialog_title")); + setText("ace-dry-dialog-temp-label", tr("ace_dry_dialog_temp")); + setText("ace-dry-dialog-time-label", tr("ace_dry_dialog_time")); + setText("ace-dry-dialog-custom-name-label", tr("ace_dry_dialog_custom_name")); + setText("ace-dry-dialog-cancel", tr("ace_dry_dialog_cancel")); + setText("ace-dry-dialog-confirm", tr("ace_dry_dialog_confirm")); + setText("ace-dry-dialog-reset-default", tr("ace_dry_dialog_reset_default")); + setText("ace-dry-dialog-save-preset", tr("ace_dry_dialog_save_restart")); aceDryDialogSyncCustomButtonNames(); // conn-btn text (nur wenn nicht im Übergangszustand) updateConnBtn(); // Slot-Edit-Dialog - setText('lbl-slot-color',T.slot_edit_color); - setText('lbl-slot-material',T.slot_edit_material); - setText('lbl-slot-profile',T.slot_edit_profile); - setText('slot-profile-hint',T.slot_edit_profile_hint); - var defOpt=document.getElementById('slot-profile-default-opt'); - if(defOpt) defOpt.textContent=T.slot_edit_profile_default; - setText('btn-slot-edit-save',T.slot_edit_save); + setText("lbl-slot-color", T.slot_edit_color); + setText("lbl-slot-material", T.slot_edit_material); + setText("lbl-slot-profile", T.slot_edit_profile); + setText("slot-profile-hint", T.slot_edit_profile_hint); + var defOpt = document.getElementById("slot-profile-default-opt"); + if (defOpt) defOpt.textContent = T.slot_edit_profile_default; + setText("btn-slot-edit-save", T.slot_edit_save); updateSlotEditFeedButton(); - var mi=document.getElementById('slot-edit-mat');if(mi)mi.setAttribute('placeholder',T.slot_edit_custom); - setText('logdir-all',T.log_dir_all); - setText('loglvl-all',T.log_dir_all); - setText('log-lbl-level',T.log_lvl_label); - setText('file-ready-btn',T.file_ready_btn); - setText('file-slots-btn',T.file_slots_btn); - setText('file-cancel-btn',T.file_cancel_btn); + var mi = document.getElementById("slot-edit-mat"); + if (mi) mi.setAttribute("placeholder", T.slot_edit_custom); + setText("logdir-all", T.log_dir_all); + setText("loglvl-all", T.log_dir_all); + setText("log-lbl-level", T.log_lvl_label); + setText("file-ready-btn", T.file_ready_btn); + setText("file-slots-btn", T.file_slots_btn); + setText("file-cancel-btn", T.file_cancel_btn); // Elements not yet covered by setText above - var settingsBtn=document.getElementById('settings-btn'); - if(settingsBtn)settingsBtn.title=T.settings_btn_tooltip||T.settings_title||'Einstellungen'; - var snpEl=document.getElementById('s-printer-name'); - if(snpEl)snpEl.placeholder=T.settings_printer_name_placeholder||'z.B. Kobra X Links'; - var sdidEl=document.getElementById('s-device-id'); - if(sdidEl)sdidEl.placeholder=T.settings_device_id_placeholder||'32 Hex-Zeichen'; - setText('d-fan-off',T.label_off||'Aus'); - setText('skip-confirm',T.skip_confirm_btn||'Überspringen'); - setText('ams-no-data',T.ams_no_data||'Keine AMS-Daten empfangen'); + var settingsBtn = document.getElementById("settings-btn"); + if (settingsBtn) + settingsBtn.title = + T.settings_btn_tooltip || T.settings_title || "Einstellungen"; + var snpEl = document.getElementById("s-printer-name"); + if (snpEl) + snpEl.placeholder = + T.settings_printer_name_placeholder || "z.B. Kobra X Links"; + var sdidEl = document.getElementById("s-device-id"); + if (sdidEl) + sdidEl.placeholder = T.settings_device_id_placeholder || "32 Hex-Zeichen"; + setText("d-fan-off", T.label_off || "Aus"); + setText("skip-confirm", T.skip_confirm_btn || "Überspringen"); + setText("ams-no-data", T.ams_no_data || "Keine AMS-Daten empfangen"); + // Dialog: Add Printer + setText("apd-title", T.apd_title || "Drucker hinzufügen"); + setText("apd-cancel", T.apd_cancel || "Abbrechen"); + setText("apd-confirm", T.apd_confirm || "Hinzufügen"); + // Dialog: File Ready (Filament/Slot selection) + setText("fd-options-title", T.fd_options_title || "Druckoptionen"); + setText("fd-cancel", T.fd_cancel || "Abbrechen"); + setText("fd-print", T.fd_print || "▶ Drucken"); + // Dialog: Web Upload Verify + setText( + "store-web-verify-title", + T.store_web_verify_title || "Datei verifizieren", + ); + setText( + "store-web-verify-msg", + T.store_web_verify_msg || + "Bitte bestätige, dass diese Datei für den Anycubic Kobra X erstellt wurde.", + ); + setText( + "store-web-verify-confirm", + T.store_web_verify_confirm || "Bestätigen", + ); + setText("store-web-verify-abort", T.store_web_verify_abort || "Abbrechen"); // GCode-Browser-Karten: Texte sind via innerHTML eingebacken, // bei Sprachwechsel komplett neu rendern. - if(typeof renderStore==='function' && typeof storeFiles!=='undefined'){ - try{ renderStore(); }catch(e){} + if (typeof renderStore === "function" && typeof storeFiles !== "undefined") { + try { + renderStore(); + } catch (e) {} } } -function setText(id,txt){var el=document.getElementById(id);if(el)el.textContent=txt;} +function setText(id, txt) { + var el = document.getElementById(id); + if (el) el.textContent = txt; +} -function ensureAceDryCards(){ - var grid=document.getElementById('d-ace-dry-grid'); - if(!grid||grid.getAttribute('data-init')==='1')return; - var html=''; - for(var i=0;i<4;i++){ - html+=''; +function ensureAceDryCards() { + var grid = document.getElementById("d-ace-dry-grid"); + if (!grid || grid.getAttribute("data-init") === "1") return; + var html = ""; + for (var i = 0; i < 4; i++) { + html += + '"; } - grid.innerHTML=html; - grid.setAttribute('data-init','1'); + grid.innerHTML = html; + grid.setAttribute("data-init", "1"); } -(function(){ - var l=_resolveInitialLanguage(); - currentLang=_normalizeLang(l); - var langSel=document.getElementById('lang-select'); - if(langSel)langSel.value=currentLang; - document.documentElement.setAttribute('lang',currentLang); +(function () { + var l = _resolveInitialLanguage(); + currentLang = _normalizeLang(l); + var langSel = document.getElementById("lang-select"); + if (langSel) langSel.value = currentLang; + document.documentElement.setAttribute("lang", currentLang); // defer until DOM ready - window.addEventListener('DOMContentLoaded',function(){ - setLanguage(currentLang).catch(function(){}); + window.addEventListener("DOMContentLoaded", function () { + setLanguage(currentLang).catch(function () {}); // Kein Drucker konfiguriert? → direkt in den Drucker-Tab (zeigt "+ Drucker hinzufügen") - fetch('/kx/printers').then(function(r){return r.json()}).then(function(d){ - if(!d.result||!d.result.length){showPanel('printers');loadPrinterTab();} - }).catch(function(){}); + fetch("/kx/printers") + .then(function (r) { + return r.json(); + }) + .then(function (d) { + if (!d.result || !d.result.length) { + showPanel("printers"); + loadPrinterTab(); + } + }) + .catch(function () {}); }); })(); // ── Panel nav ── -function showPanel(id){ - document.querySelectorAll('.panel').forEach(p=>p.classList.remove('active')); - document.getElementById('panel-'+id).classList.add('active'); - document.querySelectorAll('.nav-btn,.bnav-btn').forEach(b=>b.classList.remove('active')); - var nb=document.getElementById('nb-'+id);if(nb)nb.classList.add('active'); - var bnb=document.getElementById('bnb-'+id);if(bnb)bnb.classList.add('active'); - currentPanel=id; - if(id==='settings')openSettings(); +function showPanel(id) { + document + .querySelectorAll(".panel") + .forEach((p) => p.classList.remove("active")); + document.getElementById("panel-" + id).classList.add("active"); + document + .querySelectorAll(".nav-btn,.bnav-btn") + .forEach((b) => b.classList.remove("active")); + var nb = document.getElementById("nb-" + id); + if (nb) nb.classList.add("active"); + var bnb = document.getElementById("bnb-" + id); + if (bnb) bnb.classList.add("active"); + currentPanel = id; + if (id === "settings") openSettings(); } // Settings-Kategorie umschalten (Master-Detail) -function showSettingsCat(name){ - document.querySelectorAll('.set-group').forEach(g=>g.classList.remove('active')); - document.querySelectorAll('.set-cat').forEach(b=>b.classList.remove('active')); - var g=document.getElementById('setgrp-'+name);if(g)g.classList.add('active'); - var c=document.getElementById('setcat-'+name);if(c)c.classList.add('active'); +function showSettingsCat(name) { + document + .querySelectorAll(".set-group") + .forEach((g) => g.classList.remove("active")); + document + .querySelectorAll(".set-cat") + .forEach((b) => b.classList.remove("active")); + var g = document.getElementById("setgrp-" + name); + if (g) g.classList.add("active"); + var c = document.getElementById("setcat-" + name); + if (c) c.classList.add("active"); } // ── Console log ── -var consoleLogs=[]; -var logAutoScroll=true; -var logBadgeCount=0; -var logDirFilter='all'; // 'all'|'rx'|'tx' -var logLevelFilter='all'; // 'all'|'err'|'warn' -var logTopicFilter=''; // '' = no topic filter +var consoleLogs = []; +var logAutoScroll = true; +var logBadgeCount = 0; +var logDirFilter = "all"; // 'all'|'rx'|'tx' +var logLevelFilter = "all"; // 'all'|'err'|'warn' +var logTopicFilter = ""; // '' = no topic filter -function clog(msg,cls){ - cls=cls||'msg-info'; - var ts=new Date().toLocaleTimeString('de',{hour:'2-digit',minute:'2-digit',second:'2-digit'}); - _appendLog({ts:ts,lvl:'',name:'ui',msg:msg},cls); +function clog(msg, cls) { + cls = cls || "msg-info"; + var ts = new Date().toLocaleTimeString("de", { + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }); + _appendLog({ ts: ts, lvl: "", name: "ui", msg: msg }, cls); } -function _lvlCls(lvl){ - if(lvl==='ERROR'||lvl==='CRITICAL')return'msg-err'; - if(lvl==='WARNING')return'msg-warn'; - if(lvl==='DEBUG')return'msg-info'; - return'msg-ok'; +function _lvlCls(lvl) { + if (lvl === "ERROR" || lvl === "CRITICAL") return "msg-err"; + if (lvl === "WARNING") return "msg-warn"; + if (lvl === "DEBUG") return "msg-info"; + return "msg-ok"; } -function _appendLog(entry,forceCls){ - var cls=forceCls||_lvlCls(entry.lvl); - var label=entry.name?'['+entry.name+'] ':''; - var fullMsg=label+entry.msg; +function _appendLog(entry, forceCls) { + var cls = forceCls || _lvlCls(entry.lvl); + var label = entry.name ? "[" + entry.name + "] " : ""; + var fullMsg = label + entry.msg; // Wiederholungen als Zähler zusammenfassen (×N) statt N identische Zeilen. - var last=consoleLogs[consoleLogs.length-1]; - if(last&&last.msg===fullMsg&&last.cls===cls){ - last.count=(last.count||1)+1; - last.ts=entry.ts; // letzte Sichtung + var last = consoleLogs[consoleLogs.length - 1]; + if (last && last.msg === fullMsg && last.cls === cls) { + last.count = (last.count || 1) + 1; + last.ts = entry.ts; // letzte Sichtung renderLog(); return; } - consoleLogs.push({ts:entry.ts,msg:fullMsg,cls:cls,count:1}); - if(consoleLogs.length>500)consoleLogs.shift(); + consoleLogs.push({ ts: entry.ts, msg: fullMsg, cls: cls, count: 1 }); + if (consoleLogs.length > 500) consoleLogs.shift(); // Badge + Toast wenn Tab nicht aktiv und Fehler/Warnungen - if(currentPanel!=='console'&&(cls==='msg-err'||cls==='msg-warn')){ + if (currentPanel !== "console" && (cls === "msg-err" || cls === "msg-warn")) { logBadgeCount++; - var bc=logBadgeCount>99?'99+':logBadgeCount; - ['log-badge','log-badge-bot'].forEach(function(id){var b=document.getElementById(id);if(b){b.style.display='inline';b.textContent=bc;}}); + var bc = logBadgeCount > 99 ? "99+" : logBadgeCount; + ["log-badge", "log-badge-bot"].forEach(function (id) { + var b = document.getElementById(id); + if (b) { + b.style.display = "inline"; + b.textContent = bc; + } + }); } - if(cls==='msg-err')showToast(entry.msg.split('\n')[0]); + if (cls === "msg-err") showToast(entry.msg.split("\n")[0]); renderLog(); } // Kurze rote Snackbar bei Fehlern (auch wenn Konsole-Tab nicht offen). -var _toastTimer=null; -function showToast(msg){ - var t=document.getElementById('kx-toast'); - if(!t){ - t=document.createElement('div'); t.id='kx-toast'; - t.style.cssText='position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background:var(--err);color:#fff;padding:10px 18px;border-radius:8px;font-size:13px;z-index:9999;max-width:90vw;box-shadow:0 4px 16px rgba(0,0,0,.4);cursor:pointer'; - t.onclick=function(){showPanel('console');t.style.display='none';}; +var _toastTimer = null; +function showToast(msg) { + var t = document.getElementById("kx-toast"); + if (!t) { + t = document.createElement("div"); + t.id = "kx-toast"; + t.style.cssText = + "position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background:var(--err);color:#fff;padding:10px 18px;border-radius:8px;font-size:13px;z-index:9999;max-width:90vw;box-shadow:0 4px 16px rgba(0,0,0,.4);cursor:pointer"; + t.onclick = function () { + showPanel("console"); + t.style.display = "none"; + }; document.body.appendChild(t); } - t.textContent='⚠ '+msg; - t.style.display='block'; + t.textContent = "⚠ " + msg; + t.style.display = "block"; clearTimeout(_toastTimer); - _toastTimer=setTimeout(function(){t.style.display='none';},6000); + _toastTimer = setTimeout(function () { + t.style.display = "none"; + }, 6000); } -function setLogDir(dir){ - logDirFilter=dir; - document.querySelectorAll('.log-dir-btn').forEach(function(b){ - b.style.background=b.id==='logdir-'+dir?'var(--accent)':'var(--raised)'; - b.style.color=b.id==='logdir-'+dir?'#fff':'var(--txt2)'; +function setLogDir(dir) { + logDirFilter = dir; + document.querySelectorAll(".log-dir-btn").forEach(function (b) { + b.style.background = + b.id === "logdir-" + dir ? "var(--accent)" : "var(--raised)"; + b.style.color = b.id === "logdir-" + dir ? "#fff" : "var(--txt2)"; }); renderLog(); } -function setLogLevel(lvl){ - logLevelFilter=lvl; - document.querySelectorAll('.log-lvl-btn').forEach(function(b){ - b.style.background=b.id==='loglvl-'+lvl?'var(--accent)':'var(--raised)'; - b.style.color=b.id==='loglvl-'+lvl?'#fff':'var(--txt2)'; +function setLogLevel(lvl) { + logLevelFilter = lvl; + document.querySelectorAll(".log-lvl-btn").forEach(function (b) { + b.style.background = + b.id === "loglvl-" + lvl ? "var(--accent)" : "var(--raised)"; + b.style.color = b.id === "loglvl-" + lvl ? "#fff" : "var(--txt2)"; }); renderLog(); } -function setLogTopic(topic){ - var inp=document.getElementById('log-filter'); - var active=inp.value===topic; - inp.value=active?'':topic; - document.querySelectorAll('.log-topic-btn').forEach(function(b){ - var on=!active&&b.getAttribute('data-topic')===topic; - b.style.background=on?'var(--accent)':'var(--raised)'; - b.style.color=on?'#fff':'var(--txt2)'; +function setLogTopic(topic) { + var inp = document.getElementById("log-filter"); + var active = inp.value === topic; + inp.value = active ? "" : topic; + document.querySelectorAll(".log-topic-btn").forEach(function (b) { + var on = !active && b.getAttribute("data-topic") === topic; + b.style.background = on ? "var(--accent)" : "var(--raised)"; + b.style.color = on ? "#fff" : "var(--txt2)"; }); renderLog(); } -function renderLog(){ - var el=document.getElementById('console-log'); - if(!el)return; - var filter=(document.getElementById('log-filter')||{}).value||''; - var fl=filter.toLowerCase(); - var rows=consoleLogs.filter(function(l){ - var m=l.msg; - if(logDirFilter==='rx'&&!/ RX[ (]/.test(m))return false; - if(logDirFilter==='tx'&&!/ TX[ (]/.test(m))return false; - if(logLevelFilter==='err'&&l.cls!=='msg-err')return false; - if(logLevelFilter==='warn'&&l.cls!=='msg-err'&&l.cls!=='msg-warn')return false; - if(fl&&!m.toLowerCase().includes(fl))return false; +function renderLog() { + var el = document.getElementById("console-log"); + if (!el) return; + var filter = (document.getElementById("log-filter") || {}).value || ""; + var fl = filter.toLowerCase(); + var rows = consoleLogs.filter(function (l) { + var m = l.msg; + if (logDirFilter === "rx" && !/ RX[ (]/.test(m)) return false; + if (logDirFilter === "tx" && !/ TX[ (]/.test(m)) return false; + if (logLevelFilter === "err" && l.cls !== "msg-err") return false; + if ( + logLevelFilter === "warn" && + l.cls !== "msg-err" && + l.cls !== "msg-warn" + ) + return false; + if (fl && !m.toLowerCase().includes(fl)) return false; return true; }); - var savedScroll=logAutoScroll?null:el.scrollTop; - el.innerHTML=rows.map(function(l){ - var cnt=(l.count&&l.count>1)?' (×'+l.count+')':''; - return '
'+l.ts+''+escHtml(l.msg)+''+cnt+'
'; - }).join(''); - if(logAutoScroll)el.scrollTop=el.scrollHeight; - else if(savedScroll!==null)el.scrollTop=savedScroll; + var savedScroll = logAutoScroll ? null : el.scrollTop; + el.innerHTML = rows + .map(function (l) { + var cnt = + l.count && l.count > 1 + ? ' (×' + l.count + ")" + : ""; + return ( + '
' + + l.ts + + '' + + escHtml(l.msg) + + "" + + cnt + + "
" + ); + }) + .join(""); + if (logAutoScroll) el.scrollTop = el.scrollHeight; + else if (savedScroll !== null) el.scrollTop = savedScroll; } -function onLogScroll(){ - var el=document.getElementById('console-log'); - if(!el)return; - var atBottom=el.scrollHeight-el.scrollTop-el.clientHeight<30; - if(!atBottom&&logAutoScroll){setAutoScroll(false);} -} -function toggleAutoScroll(){ - setAutoScroll(!logAutoScroll); - if(logAutoScroll){var el=document.getElementById('console-log');if(el)el.scrollTop=el.scrollHeight;} -} -function setAutoScroll(on){ - logAutoScroll=on; - var btn=document.getElementById('btn-autoscroll'); - if(btn){btn.style.background=on?'var(--accent)':'var(--raised)';btn.style.color=on?'#fff':'var(--txt2)';} -} -function clearLogBadge(){ - logBadgeCount=0; - ['log-badge','log-badge-bot'].forEach(function(id){var b=document.getElementById(id);if(b)b.style.display='none';}); -} -function escHtml(s){return s.replace(/&/g,'&').replace(//g,'>');} -// SSE server-log stream -(function(){ - function connect(){ - var es=new EventSource('/api/log/stream'); - es.onmessage=function(e){try{_appendLog(JSON.parse(e.data));}catch(_){}}; - es.onerror=function(){es.close();setTimeout(connect,3000);}; +function onLogScroll() { + var el = document.getElementById("console-log"); + if (!el) return; + var atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 30; + if (!atBottom && logAutoScroll) { + setAutoScroll(false); } - window.addEventListener('DOMContentLoaded',connect); +} +function toggleAutoScroll() { + setAutoScroll(!logAutoScroll); + if (logAutoScroll) { + var el = document.getElementById("console-log"); + if (el) el.scrollTop = el.scrollHeight; + } +} +function setAutoScroll(on) { + logAutoScroll = on; + var btn = document.getElementById("btn-autoscroll"); + if (btn) { + btn.style.background = on ? "var(--accent)" : "var(--raised)"; + btn.style.color = on ? "#fff" : "var(--txt2)"; + } +} +function clearLogBadge() { + logBadgeCount = 0; + ["log-badge", "log-badge-bot"].forEach(function (id) { + var b = document.getElementById(id); + if (b) b.style.display = "none"; + }); +} +function escHtml(s) { + return s.replace(/&/g, "&").replace(//g, ">"); +} +// SSE server-log stream +(function () { + function connect() { + var es = new EventSource("/api/log/stream"); + es.onmessage = function (e) { + try { + _appendLog(JSON.parse(e.data)); + } catch (_) {} + }; + es.onerror = function () { + es.close(); + setTimeout(connect, 3000); + }; + } + window.addEventListener("DOMContentLoaded", connect); })(); // ── Helpers ── -function fmtTime(s){if(!s||s<0)return'–';var m=Math.floor(s/60),h=Math.floor(m/60);m%=60;return h>0?h+'h '+m+'m':m+'m'} -function fmtHmsFromSec(total){ - total=Math.max(0,parseInt(total||0,10)); - var h=Math.floor(total/3600); - var mm=Math.floor((total%3600)/60); - var ss=total%60; - return h+':'+String(mm).padStart(2,'0')+':'+String(ss).padStart(2,'0'); +function fmtTime(s) { + if (!s || s < 0) return "–"; + var m = Math.floor(s / 60), + h = Math.floor(m / 60); + m %= 60; + return h > 0 ? h + "h " + m + "m" : m + "m"; +} +function fmtHmsFromSec(total) { + total = Math.max(0, parseInt(total || 0, 10)); + var h = Math.floor(total / 3600); + var mm = Math.floor((total % 3600) / 60); + var ss = total % 60; + return ( + h + ":" + String(mm).padStart(2, "0") + ":" + String(ss).padStart(2, "0") + ); +} +function post(url, body) { + return fetch(_apiUrl(url), { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); +} +function clamp(v, lo, hi) { + return Math.min(hi, Math.max(lo, v)); } -function post(url,body){return fetch(_apiUrl(url),{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)})} -function clamp(v,lo,hi){return Math.min(hi,Math.max(lo,v))} // ── Apply state to DOM ── -function applyState(){ - var s=S; +function applyState() { + var s = S; _syncAceDryPresetsFromServer(s.ace_dry_presets); // connection error banner – nur wenn überhaupt ein Drucker konfiguriert ist - var banner=document.getElementById('conn-error-banner'); - if(banner){if(s.connection_error&&_printers.length>0){banner.textContent='⚠ '+tr('lbl_conn_error')+' '+s.connection_error;banner.style.display='block';}else{banner.style.display='none';}} - var bannerVisible=false; - var frb=document.getElementById('file-ready-banner'); - if(frb){ - var shouldAutoOpen=(s.print_start_dialog===undefined?true:!!s.print_start_dialog); - if(s.file_ready&&s.print_state==='standby'){ - document.getElementById('file-ready-name').textContent=s.file_ready; + var banner = document.getElementById("conn-error-banner"); + if (banner) { + if (s.connection_error && _printers.length > 0) { + banner.textContent = + "⚠ " + tr("lbl_conn_error") + " " + s.connection_error; + banner.style.display = "block"; + } else { + banner.style.display = "none"; + } + } + var bannerVisible = false; + var frb = document.getElementById("file-ready-banner"); + if (frb) { + var shouldAutoOpen = + s.print_start_dialog === undefined ? true : !!s.print_start_dialog; + 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 (_fdAutoOpenedFile && _fdAutoOpenedFile !== s.file_ready) { + _fdUserCancelled = false; + sessionStorage.removeItem("fdUserCancelled"); + sessionStorage.removeItem("fdAutoOpenedFile"); } - if(shouldAutoOpen){ + 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); + frb.style.display = "none"; + if ( + !_fdDialogOpen && + !_fdUserCancelled && + _fdAutoOpenedFile !== s.file_ready + ) { + _fdAutoOpenedFile = s.file_ready; + startReadyFileWithSlots(s.file_ready, true); } } else { - frb.style.display='flex'; - bannerVisible=true; + frb.style.display = "flex"; + bannerVisible = true; } - }else{frb.style.display='none';} + } else { + frb.style.display = "none"; + } } // skip-button (mid-print) – nur sichtbar wenn aktuell gedruckt wird - var printing=(s.print_state==='printing'||s.print_state==='paused'); - var skipBtn=document.getElementById('d-btn-skip'); - if(skipBtn) skipBtn.style.display=printing?'':'none'; + var printing = s.print_state === "printing" || s.print_state === "paused"; + var skipBtn = document.getElementById("d-btn-skip"); + if (skipBtn) skipBtn.style.display = printing ? "" : "none"; // Pause/Stopp-Buttons nur bei aktivem Druck zeigen (sonst verwirrend wenn // der Drucker idle ist). Pause-Button rendert sich passend zum State um. - var ctrlBtns=document.getElementById('d-ctrl-btns'); - if(ctrlBtns) ctrlBtns.style.display=printing?'':'none'; + var ctrlBtns = document.getElementById("d-ctrl-btns"); + if (ctrlBtns) ctrlBtns.style.display = printing ? "" : "none"; updatePauseResumeBtn(); // 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; + 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; // 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. - var idleBtns=document.getElementById('d-idle-btns'); - if(idleBtns){ - var showIdle=(!printing && _lastLoadedFile && !bannerVisible); - idleBtns.style.display=showIdle?'':'none'; - if(showIdle){ - var dfn=document.getElementById('d-fname'); - if(dfn && (!dfn.textContent || dfn.textContent==='–')){ - dfn.textContent=_lastLoadedFile;dfn.title=_lastLoadedFile; + var idleBtns = document.getElementById("d-idle-btns"); + if (idleBtns) { + var showIdle = !printing && _lastLoadedFile && !bannerVisible; + idleBtns.style.display = showIdle ? "" : "none"; + if (showIdle) { + var dfn = document.getElementById("d-fname"); + if (dfn && (!dfn.textContent || dfn.textContent === "–")) { + dfn.textContent = _lastLoadedFile; + dfn.title = _lastLoadedFile; } } } // header - var b=document.getElementById('h-badge'); - b.className='hbadge '+s.print_state; - document.getElementById('h-state').textContent=T['kobra_'+s.kobra_state]||s.kobra_state||T.header_status_standby; - var _pn=_printers.length===0?'–':((_activePrinter&&_activePrinter.name)||s.printer_name); - var _el=document.getElementById('h-pname');if(_el)_el.textContent=_pn; - var _el2=document.getElementById('h-pname-single');if(_el2)_el2.textContent=_pn; - var hv=document.getElementById('h-version');if(hv&&s.version)hv.textContent='v'+s.version; - + var b = document.getElementById("h-badge"); + b.className = "hbadge " + s.print_state; + document.getElementById("h-state").textContent = + T["kobra_" + s.kobra_state] || s.kobra_state || T.header_status_standby; + var _pn = + _printers.length === 0 + ? "–" + : (_activePrinter && _activePrinter.name) || s.printer_name; + var _el = document.getElementById("h-pname"); + if (_el) _el.textContent = _pn; + var _el2 = document.getElementById("h-pname-single"); + if (_el2) _el2.textContent = _pn; + var hv = document.getElementById("h-version"); + if (hv && s.version) hv.textContent = "v" + s.version; // temps - var nt=document.getElementById('d-nt');if(nt)nt.textContent=s.nozzle_temp.toFixed(1); - var ntt=document.getElementById('d-nt-t');if(ntt)ntt.textContent=s.nozzle_target.toFixed(0); - var bt=document.getElementById('d-bt');if(bt)bt.textContent=s.bed_temp.toFixed(1); - var btt=document.getElementById('d-bt-t');if(btt)btt.textContent=s.bed_target.toFixed(0); + var nt = document.getElementById("d-nt"); + if (nt) nt.textContent = s.nozzle_temp.toFixed(1); + var ntt = document.getElementById("d-nt-t"); + if (ntt) ntt.textContent = s.nozzle_target.toFixed(0); + var bt = document.getElementById("d-bt"); + if (bt) bt.textContent = s.bed_temp.toFixed(1); + var btt = document.getElementById("d-bt-t"); + if (btt) btt.textContent = s.bed_target.toFixed(0); // temp bars (dashboard) - var nb=document.getElementById('d-ntbar');if(nb)nb.style.width=clamp(s.nozzle_temp/300*100,0,100)+'%'; - var bb=document.getElementById('d-btbar');if(bb)bb.style.width=clamp(s.bed_temp/120*100,0,100)+'%'; + var nb = document.getElementById("d-ntbar"); + if (nb) nb.style.width = clamp((s.nozzle_temp / 300) * 100, 0, 100) + "%"; + var bb = document.getElementById("d-btbar"); + if (bb) bb.style.width = clamp((s.bed_temp / 120) * 100, 0, 100) + "%"; // progress - var pct=Math.round(s.progress*100); - var dpct=document.getElementById('d-pct');if(dpct)dpct.textContent=pct; - var dpbar=document.getElementById('d-pbar');if(dpbar)dpbar.style.width=pct+'%'; + var pct = Math.round(s.progress * 100); + var dpct = document.getElementById("d-pct"); + if (dpct) dpct.textContent = pct; + var dpbar = document.getElementById("d-pbar"); + if (dpbar) dpbar.style.width = pct + "%"; - var layers=s.curr_layer&&s.total_layers?'L '+s.curr_layer+' / '+s.total_layers:'–'; - var dlayers=document.getElementById('d-layers');if(dlayers)dlayers.textContent=layers; - var dzpos=document.getElementById('d-zpos');if(dzpos)dzpos.textContent=s.z_mm>0?s.z_mm.toFixed(2)+' mm':'–'; + var layers = + s.curr_layer && s.total_layers + ? "L " + s.curr_layer + " / " + s.total_layers + : "–"; + var dlayers = document.getElementById("d-layers"); + if (dlayers) dlayers.textContent = layers; + var dzpos = document.getElementById("d-zpos"); + if (dzpos) dzpos.textContent = s.z_mm > 0 ? s.z_mm.toFixed(2) + " mm" : "–"; - var delapsed=document.getElementById('d-elapsed');if(delapsed)delapsed.textContent=fmtTime(s.print_duration); - var dremain=document.getElementById('d-remain');if(dremain)dremain.textContent=s.remain_time>0?fmtTime(s.remain_time):'–'; - var dslrow=document.getElementById('d-slicer-row'); - var dsltime=document.getElementById('d-slicer-time'); - if(dslrow&&dsltime){ - if(s.slicer_time>0){dslrow.style.display='';dsltime.textContent=fmtTime(s.slicer_time);} - else{dslrow.style.display='none';} + var delapsed = document.getElementById("d-elapsed"); + if (delapsed) delapsed.textContent = fmtTime(s.print_duration); + var dremain = document.getElementById("d-remain"); + if (dremain) + dremain.textContent = s.remain_time > 0 ? fmtTime(s.remain_time) : "–"; + var dslrow = document.getElementById("d-slicer-row"); + var dsltime = document.getElementById("d-slicer-time"); + if (dslrow && dsltime) { + if (s.slicer_time > 0) { + dslrow.style.display = ""; + dsltime.textContent = fmtTime(s.slicer_time); + } else { + dslrow.style.display = "none"; + } } - var fn=s.filename||'–'; - var dfname=document.getElementById('d-fname');if(dfname){dfname.textContent=fn;dfname.title=fn}; - var pfname=document.getElementById('p-fname');if(pfname){pfname.textContent=fn;pfname.title=fn}; - var cfo=document.getElementById('cam-fname');if(cfo)cfo.textContent=fn!=='–'?fn:''; + var fn = s.filename || "–"; + var dfname = document.getElementById("d-fname"); + if (dfname) { + dfname.textContent = fn; + dfname.title = fn; + } + var pfname = document.getElementById("p-fname"); + if (pfname) { + pfname.textContent = fn; + pfname.title = fn; + } + var cfo = document.getElementById("cam-fname"); + if (cfo) cfo.textContent = fn !== "–" ? fn : ""; // thumbnail - var thumb=document.getElementById('d-thumbnail'); - if(thumb){ - if(s.thumbnail){ - thumb.src='data:image/png;base64,'+s.thumbnail; - thumb.style.display='block'; + var thumb = document.getElementById("d-thumbnail"); + if (thumb) { + if (s.thumbnail) { + thumb.src = "data:image/png;base64," + s.thumbnail; + thumb.style.display = "block"; } else { - thumb.style.display='none'; - thumb.src=''; + thumb.style.display = "none"; + thumb.src = ""; } } // light/fan sync - document.getElementById('d-light-toggle').checked=s.light_on; - var dfan=document.getElementById('d-fan');if(dfan)dfan.value=s.fan_speed; - var dfanval=document.getElementById('d-fan-val');if(dfanval)dfanval.textContent=s.fan_speed; + document.getElementById("d-light-toggle").checked = s.light_on; + var dfan = document.getElementById("d-fan"); + if (dfan) dfan.value = s.fan_speed; + var dfanval = document.getElementById("d-fan-val"); + if (dfanval) dfanval.textContent = s.fan_speed; // speed mode buttons - var spdWidths={1:25,2:55,3:90}; - [1,2,3].forEach(function(m){ - var b=document.getElementById('d-spd-'+m); - if(b) b.classList.toggle('spd-active', s.print_speed_mode===m); + var spdWidths = { 1: 25, 2: 55, 3: 90 }; + [1, 2, 3].forEach(function (m) { + var b = document.getElementById("d-spd-" + m); + if (b) b.classList.toggle("spd-active", s.print_speed_mode === m); }); - var spdBar=document.getElementById('d-spd-bar'); - if(spdBar) spdBar.style.width=(spdWidths[s.print_speed_mode]||55)+'%'; + var spdBar = document.getElementById("d-spd-bar"); + if (spdBar) spdBar.style.width = (spdWidths[s.print_speed_mode] || 55) + "%"; - var amsTitle=document.getElementById('d-card-ams'); - if(amsTitle){ - var baseTitle=tr('card_ams'); - var modeMap={toolhead:'Toolhead',ace_direct:'ACE Direct',ace_hub:'ACE Hub'}; - var modeTxt=modeMap[s.filament_mode]||''; - amsTitle.textContent=modeTxt?(baseTitle+' - '+modeTxt):baseTitle; + var amsTitle = document.getElementById("d-card-ams"); + if (amsTitle) { + var baseTitle = tr("card_ams"); + var modeMap = { + toolhead: "Toolhead", + ace_direct: "ACE Direct", + ace_hub: "ACE Hub", + }; + var modeTxt = modeMap[s.filament_mode] || ""; + amsTitle.textContent = modeTxt ? baseTitle + " - " + modeTxt : baseTitle; } ensureAceDryCards(); - var dry=s.ace_drying||{status:0,target_temp:0,duration:0,remain_time:0,humidity:null,current_temp:null,units:[]}; - var units=(dry.units||[]); - var unitMap={}; - units.forEach(function(u){var id=Number(u.id);if(id>=0&&id<=3)unitMap[id]=u;}); - var aceMode=s.filament_mode==='ace_direct'||s.filament_mode==='ace_hub'; - var detected=(s.ace_units||[]).filter(function(id){return id>=0&&id<=3;}); - if(!detected.length){ - Object.keys(unitMap).forEach(function(k){detected.push(Number(k));}); + var dry = s.ace_drying || { + status: 0, + target_temp: 0, + duration: 0, + remain_time: 0, + humidity: null, + current_temp: null, + units: [], + }; + var units = dry.units || []; + var unitMap = {}; + units.forEach(function (u) { + var id = Number(u.id); + if (id >= 0 && id <= 3) unitMap[id] = u; + }); + var aceMode = + s.filament_mode === "ace_direct" || s.filament_mode === "ace_hub"; + var detected = (s.ace_units || []).filter(function (id) { + return id >= 0 && id <= 3; + }); + if (!detected.length) { + Object.keys(unitMap).forEach(function (k) { + detected.push(Number(k)); + }); } - if(!detected.length){ - (s.ams_slots||[]).forEach(function(sl){var id=Number(sl.box_id);if(id>=0&&id<=3&&detected.indexOf(id)<0)detected.push(id);}); + if (!detected.length) { + (s.ams_slots || []).forEach(function (sl) { + var id = Number(sl.box_id); + if (id >= 0 && id <= 3 && detected.indexOf(id) < 0) detected.push(id); + }); } - detected.sort(function(a,b){return a-b;}); - var aceWrap=document.getElementById('d-ace-dry-wrap'); - if(aceWrap)aceWrap.style.display=(aceMode&&detected.length)?'contents':'none'; - for(var i=0;i<4;i++){ - var card=document.getElementById('d-ace-dry-card-'+i); - if(!card)continue; - var show=aceMode&&detected.indexOf(i)>=0; - card.style.display=show?'':'none'; - if(!show)continue; - var ud=unitMap[i]||dry; - var refillToggle=document.getElementById('ace-auto-refill-toggle-'+i); - var autoFeedMap=s.ace_auto_feed||{}; - if(refillToggle&&!_aceAutoFeedPending[i]){ - var afVal=autoFeedMap.hasOwnProperty(String(i))?Number(autoFeedMap[String(i)]):(_aceAutoRefillGet(i)?1:0); - refillToggle.checked=afVal===1; + detected.sort(function (a, b) { + return a - b; + }); + var aceWrap = document.getElementById("d-ace-dry-wrap"); + if (aceWrap) + aceWrap.style.display = aceMode && detected.length ? "contents" : "none"; + for (var i = 0; i < 4; i++) { + var card = document.getElementById("d-ace-dry-card-" + i); + if (!card) continue; + var show = aceMode && detected.indexOf(i) >= 0; + card.style.display = show ? "" : "none"; + if (!show) continue; + var ud = unitMap[i] || dry; + var refillToggle = document.getElementById("ace-auto-refill-toggle-" + i); + var autoFeedMap = s.ace_auto_feed || {}; + if (refillToggle && !_aceAutoFeedPending[i]) { + var afVal = autoFeedMap.hasOwnProperty(String(i)) + ? Number(autoFeedMap[String(i)]) + : _aceAutoRefillGet(i) + ? 1 + : 0; + refillToggle.checked = afVal === 1; } - var dryToggle=document.getElementById('ace-dry-enable-toggle-'+i); - if(dryToggle)dryToggle.checked=Number(ud.status||0)>0; - var hh=document.getElementById('d-ace-dry-humidity-'+i); - if(hh){ - var hv=(ud.humidity===null||ud.humidity===undefined||ud.humidity==='')?null:Number(ud.humidity); - hh.textContent=(hv===null||Number.isNaN(hv))?'-':(Math.round(hv)+'%'); + var dryToggle = document.getElementById("ace-dry-enable-toggle-" + i); + if (dryToggle) dryToggle.checked = Number(ud.status || 0) > 0; + var hh = document.getElementById("d-ace-dry-humidity-" + i); + if (hh) { + var hv = + ud.humidity === null || ud.humidity === undefined || ud.humidity === "" + ? null + : Number(ud.humidity); + hh.textContent = + hv === null || Number.isNaN(hv) ? "-" : Math.round(hv) + "%"; } - var ht=document.getElementById('d-ace-dry-current-temp-'+i); - if(ht){ - var ct=(ud.current_temp===null||ud.current_temp===undefined||ud.current_temp==='')?null:Number(ud.current_temp); - ht.textContent=(ct===null||Number.isNaN(ct))?'-':(ct.toFixed(1)+'°C'); + var ht = document.getElementById("d-ace-dry-current-temp-" + i); + if (ht) { + var ct = + ud.current_temp === null || + ud.current_temp === undefined || + ud.current_temp === "" + ? null + : Number(ud.current_temp); + ht.textContent = + ct === null || Number.isNaN(ct) ? "-" : ct.toFixed(1) + "°C"; } - var prof=_aceDryProfileGet(i); - var useSec=(Number(ud.status||0)>0&&Number(ud.remain_time)>0) - ?Number(ud.remain_time||0)*60 - :prof.duration_sec; - var showTemp=(Number(ud.status||0)>0&&Number(ud.target_temp)>0)?Number(ud.target_temp):prof.temp; - var dryTempEl=document.getElementById('d-ace-dry-target-'+i); - if(dryTempEl)dryTempEl.textContent=showTemp+'°C'; - var dryTimeEl=document.getElementById('d-ace-dry-time-'+i); - if(dryTimeEl)dryTimeEl.textContent=fmtHmsFromSec(useSec); + var prof = _aceDryProfileGet(i); + var useSec = + Number(ud.status || 0) > 0 && Number(ud.remain_time) > 0 + ? Number(ud.remain_time || 0) * 60 + : prof.duration_sec; + var showTemp = + Number(ud.status || 0) > 0 && Number(ud.target_temp) > 0 + ? Number(ud.target_temp) + : prof.temp; + var dryTempEl = document.getElementById("d-ace-dry-target-" + i); + if (dryTempEl) dryTempEl.textContent = showTemp + "°C"; + var dryTimeEl = document.getElementById("d-ace-dry-time-" + i); + if (dryTimeEl) dryTimeEl.textContent = fmtHmsFromSec(useSec); } // AMS - if(s.ams_slots&&s.ams_slots.length){ - window._amsSlots=s.ams_slots; + if (s.ams_slots && s.ams_slots.length) { + window._amsSlots = s.ams_slots; // Group by box_id (-1=Toolhead, 0=ACE 1, 1=ACE 2, ...) - var boxMap={}; - s.ams_slots.forEach(function(slot,i){ - var bid=slot.box_id!=null?slot.box_id:-1; - if(!boxMap[bid])boxMap[bid]=[]; - boxMap[bid].push({slot:slot,arrIdx:i}); + var boxMap = {}; + s.ams_slots.forEach(function (slot, i) { + var bid = slot.box_id != null ? slot.box_id : -1; + if (!boxMap[bid]) boxMap[bid] = []; + boxMap[bid].push({ slot: slot, arrIdx: i }); }); - var boxIds=Object.keys(boxMap).map(Number).sort(function(a,b){return a-b}); - var acePresent=boxIds.some(function(b){return b>=0;}); - var html=''; - boxIds.forEach(function(bid){ - var entries=boxMap[bid]; - var label=bid===-1 - ?(acePresent?'Toolhead (Slots 1–3)':'Toolhead') - :('ACE '+(bid+1)); - html+='
' - +'
'+label+'
' - +'
'; - entries.forEach(function(e){ - var slot=e.slot;var i=e.arrIdx; - var empty=slot.status!==5; - var rgb=empty?[80,80,80]:(Array.isArray(slot.color)?slot.color:[128,128,128]); - var col='rgb('+rgb[0]+','+rgb[1]+','+rgb[2]+')'; - var globalIdx=slot.global_index!=null?slot.global_index:i; - var active=slot.status===1||slot.active; - var loaded=(s.ams_loaded_slot!=null&&s.ams_loaded_slot>=0&&globalIdx===s.ams_loaded_slot); - var activity=(slot.activity||''); - var pct=empty?T.ams_empty:(slot.consumables_percent!=null?slot.consumables_percent+'%':'–'); - var slotLabel=T.label_slot+' '+(globalIdx+1); + var boxIds = Object.keys(boxMap) + .map(Number) + .sort(function (a, b) { + return a - b; + }); + var acePresent = boxIds.some(function (b) { + return b >= 0; + }); + var html = ""; + boxIds.forEach(function (bid) { + var entries = boxMap[bid]; + var label = + bid === -1 + ? acePresent + ? "Toolhead (Slots 1–3)" + : "Toolhead" + : "ACE " + (bid + 1); + html += + '
' + + '
' + + label + + "
" + + '
'; + entries.forEach(function (e) { + var slot = e.slot; + var i = e.arrIdx; + var empty = slot.status !== 5; + var rgb = empty + ? [80, 80, 80] + : Array.isArray(slot.color) + ? slot.color + : [128, 128, 128]; + var col = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")"; + var globalIdx = slot.global_index != null ? slot.global_index : i; + var active = slot.status === 1 || slot.active; + var loaded = + s.ams_loaded_slot != null && + s.ams_loaded_slot >= 0 && + globalIdx === s.ams_loaded_slot; + 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||'–'); + 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 vendorBadge=''; - if(!empty && profile && profile.vendor){ - var tt=(profile.name||'')+(profile.id?' ('+profile.id+')':''); - vendorBadge='
'+profile.vendor+'
'; + var materialLabel = empty + ? "–" + : profile && profile.name + ? profile.name + : genericType; + var vendorBadge = ""; + if (!empty && profile && profile.vendor) { + var tt = + (profile.name || "") + (profile.id ? " (" + profile.id + ")" : ""); + vendorBadge = + '
' + + profile.vendor + + "
"; } - html+='
' - +'
' - +'
'+materialLabel+'
' - +vendorBadge - +'
'+slotLabel+'
' - +'
'+pct+'
' - +'
' - +'
'; + html += + '
' + + '
' + + '
' + + materialLabel + + "
" + + vendorBadge + + '
' + + slotLabel + + "
" + + '
' + + pct + + "
" + + '
' + + "
"; }); - if(bid===-1&&acePresent){ - html+='
' - +'
ACE
' - +'
'; + if (bid === -1 && acePresent) { + html += + '
' + + '
ACE
' + + "
"; } - html+='
'; + html += "
"; }); - document.getElementById('ams-slots').innerHTML=html; + document.getElementById("ams-slots").innerHTML = html; } // camera overlay - var co=document.getElementById('cam-overlay'); - if(co)co.style.display=(s.print_state==='printing'&&camOn)?'block':'none'; + var co = document.getElementById("cam-overlay"); + 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 && + s.camera_on_print + ) { camStart(); } // reset user-stopped flag when print ends so next print auto-starts again - if(s.print_state!=='printing'){ - camUserStopped=false; + if (s.print_state !== "printing") { + camUserStopped = false; } updateConnBtn(); } -function updateConnBtn(){ - var btn=document.getElementById('conn-btn'); - if(!btn)return; - var offline=S.kobra_state==='offline'; - if(offline){ - btn.className='conn-btn disconnected'; - btn.textContent=tr('btn_connect'); +function updateConnBtn() { + var btn = document.getElementById("conn-btn"); + if (!btn) return; + var offline = S.kobra_state === "offline"; + if (offline) { + btn.className = "conn-btn disconnected"; + btn.textContent = tr("btn_connect"); } else { - btn.className='conn-btn connected'; - btn.textContent=tr('btn_disconnect'); + btn.className = "conn-btn connected"; + btn.textContent = tr("btn_disconnect"); } } -function toggleConnection(){ - var btn=document.getElementById('conn-btn'); - var offline=S.kobra_state==='offline'; - btn.disabled=true; - btn.textContent='…'; - var url=offline?'/api/connect':'/api/disconnect'; - post(url,{}).then(function(r){return r.json()}).then(function(r){ - btn.disabled=false; - if(r.error)addLog('Error: '+r.error); - }).catch(function(){btn.disabled=false;}); +function toggleConnection() { + var btn = document.getElementById("conn-btn"); + var offline = S.kobra_state === "offline"; + btn.disabled = true; + btn.textContent = "…"; + var url = offline ? "/api/connect" : "/api/disconnect"; + post(url, {}) + .then(function (r) { + return r.json(); + }) + .then(function (r) { + btn.disabled = false; + if (r.error) addLog("Error: " + r.error); + }) + .catch(function () { + btn.disabled = false; + }); } // ── Temp history + chart ── -function updateHistory(){ +function updateHistory() { tempHistory.n.push(S.nozzle_temp); tempHistory.b.push(S.bed_temp); - if(tempHistory.n.length>60)tempHistory.n.shift(); - if(tempHistory.b.length>60)tempHistory.b.shift(); - drawChart('d-chart',tempHistory,[{data:tempHistory.n,color:'#00c8ff',max:300},{data:tempHistory.b,color:'#ff6b35',max:120}]); + if (tempHistory.n.length > 60) tempHistory.n.shift(); + if (tempHistory.b.length > 60) tempHistory.b.shift(); + drawChart("d-chart", tempHistory, [ + { data: tempHistory.n, color: "#00c8ff", max: 300 }, + { data: tempHistory.b, color: "#ff6b35", max: 120 }, + ]); } -function drawChart(id,_,series){ - var canvas=document.getElementById(id);if(!canvas)return; - var ctx=canvas.getContext('2d'); - var W=canvas.offsetWidth*window.devicePixelRatio||canvas.width; - var H=canvas.offsetHeight*window.devicePixelRatio||canvas.height; - canvas.width=W;canvas.height=H; - ctx.clearRect(0,0,W,H); - series.forEach(function(s){ - var data=s.data;if(!data.length)return; - var max=s.max; - ctx.beginPath();ctx.strokeStyle=s.color;ctx.lineWidth=2;ctx.lineJoin='round'; - data.forEach(function(v,i){ - var x=i/(Math.max(data.length-1,1))*(W-4)+2; - var y=H-4-(v/max)*(H-8); - if(i===0)ctx.moveTo(x,y);else ctx.lineTo(x,y); +function drawChart(id, _, series) { + var canvas = document.getElementById(id); + if (!canvas) return; + var ctx = canvas.getContext("2d"); + var W = canvas.offsetWidth * window.devicePixelRatio || canvas.width; + var H = canvas.offsetHeight * window.devicePixelRatio || canvas.height; + canvas.width = W; + canvas.height = H; + ctx.clearRect(0, 0, W, H); + series.forEach(function (s) { + var data = s.data; + if (!data.length) return; + var max = s.max; + ctx.beginPath(); + ctx.strokeStyle = s.color; + ctx.lineWidth = 2; + ctx.lineJoin = "round"; + data.forEach(function (v, i) { + var x = (i / Math.max(data.length - 1, 1)) * (W - 4) + 2; + var y = H - 4 - (v / max) * (H - 8); + if (i === 0) ctx.moveTo(x, y); + else ctx.lineTo(x, y); }); ctx.stroke(); }); } // ── Notifications ── -var _notifRows=[]; -var _NOTIF_EVENTS=['started','finished','failed','cancelled','paused','progress']; -function notifRenderList(entries){ - _notifRows=entries.map(function(e){return {url:e.url||'',events:e.events||[],include_image:!!e.include_image};}); +var _notifRows = []; +var _NOTIF_EVENTS = [ + "started", + "finished", + "failed", + "cancelled", + "paused", + "progress", +]; +function notifRenderList(entries) { + _notifRows = entries.map(function (e) { + return { + url: e.url || "", + events: e.events || [], + include_image: !!e.include_image, + }; + }); notifRefreshDOM(); } -function notifRefreshDOM(){ - var container=document.getElementById('notif-list'); - if(!container)return; - if(!_notifRows.length){ - container.innerHTML='
'+(T.settings_notif_empty||'No notifications configured.')+'
'; +function notifRefreshDOM() { + var container = document.getElementById("notif-list"); + if (!container) return; + if (!_notifRows.length) { + container.innerHTML = + '
' + + (T.settings_notif_empty || "No notifications configured.") + + "
"; return; } - container.innerHTML=_notifRows.map(function(row,idx){ - var evChecks=_NOTIF_EVENTS.map(function(ev){ - var checked=row.events.indexOf(ev)>=0?'checked':''; - var lbl=T['settings_notif_ev_'+ev]||ev; - return ''; - }).join(' '); - var imgCheck=''; - return '
' - +'
' - +'' - +'' - +'' - +'
' - +'
'+evChecks+imgCheck+'
' - +'
' - +'
'; - }).join(''); + container.innerHTML = _notifRows + .map(function (row, idx) { + var evChecks = _NOTIF_EVENTS + .map(function (ev) { + var checked = row.events.indexOf(ev) >= 0 ? "checked" : ""; + var lbl = T["settings_notif_ev_" + ev] || ev; + return ( + '" + ); + }) + .join(" "); + var imgCheck = + '"; + return ( + '
' + + '
' + + '' + + '" + + '' + + "
" + + '
' + + evChecks + + imgCheck + + "
" + + '
' + + "
" + ); + }) + .join(""); } -function _escHtml(s){return (s||'').replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"');} -function notifAddRow(){ - _notifRows.push({url:'',events:['finished','failed'],include_image:false}); +function _escHtml(s) { + return (s || "") + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """); +} +function notifAddRow() { + _notifRows.push({ + url: "", + events: ["finished", "failed"], + include_image: false, + }); notifRefreshDOM(); } -function notifRemoveRow(idx){ - _notifRows.splice(idx,1); +function notifRemoveRow(idx) { + _notifRows.splice(idx, 1); notifRefreshDOM(); } -function notifSetUrl(idx,val){ - if(_notifRows[idx])_notifRows[idx].url=val; +function notifSetUrl(idx, val) { + if (_notifRows[idx]) _notifRows[idx].url = val; } -function notifToggleEvent(idx,ev){ - if(!_notifRows[idx])return; - var evs=_notifRows[idx].events; - var pos=evs.indexOf(ev); - if(pos>=0)evs.splice(pos,1);else evs.push(ev); +function notifToggleEvent(idx, ev) { + if (!_notifRows[idx]) return; + var evs = _notifRows[idx].events; + var pos = evs.indexOf(ev); + if (pos >= 0) evs.splice(pos, 1); + else evs.push(ev); } -function notifToggleImage(idx){ - if(_notifRows[idx])_notifRows[idx].include_image=!_notifRows[idx].include_image; +function notifToggleImage(idx) { + if (_notifRows[idx]) + _notifRows[idx].include_image = !_notifRows[idx].include_image; } -function notifCollect(){ - return _notifRows.filter(function(r){return r.url.trim();}).map(function(r){ - return {url:r.url.trim(),events:r.events,include_image:!!r.include_image}; - }); +function notifCollect() { + return _notifRows + .filter(function (r) { + return r.url.trim(); + }) + .map(function (r) { + return { + url: r.url.trim(), + events: r.events, + include_image: !!r.include_image, + }; + }); } -function notifTest(idx){ - var row=_notifRows[idx]; - if(!row||!row.url.trim())return; - var btn=document.getElementById('notif-test-btn-'+idx); - var status=document.getElementById('notif-test-status-'+idx); - if(btn)btn.disabled=true; - if(status){status.textContent='…';status.style.color='var(--txt2)';} - post('/api/notifications/test',{url:row.url.trim()}).then(function(r){return r.json();}).then(function(d){ - if(btn)btn.disabled=false; - if(status){ - if(d&&d.status==='ok'){status.textContent='✓ '+(T.settings_notif_test_ok||'Sent');status.style.color='var(--ok)';} - else{status.textContent='✗ '+(d&&d.message?d.message:(T.settings_notif_test_fail||'Failed'));status.style.color='var(--err)';} - } - }).catch(function(e){ - if(btn)btn.disabled=false; - if(status){status.textContent='✗ '+e;status.style.color='var(--err)';} - }); +function notifTest(idx) { + var row = _notifRows[idx]; + if (!row || !row.url.trim()) return; + var btn = document.getElementById("notif-test-btn-" + idx); + var status = document.getElementById("notif-test-status-" + idx); + if (btn) btn.disabled = true; + if (status) { + status.textContent = "…"; + status.style.color = "var(--txt2)"; + } + post("/api/notifications/test", { url: row.url.trim() }) + .then(function (r) { + return r.json(); + }) + .then(function (d) { + if (btn) btn.disabled = false; + if (status) { + if (d && d.status === "ok") { + status.textContent = "✓ " + (T.settings_notif_test_ok || "Sent"); + status.style.color = "var(--ok)"; + } else { + status.textContent = + "✗ " + + (d && d.message + ? d.message + : T.settings_notif_test_fail || "Failed"); + status.style.color = "var(--err)"; + } + } + }) + .catch(function (e) { + if (btn) btn.disabled = false; + if (status) { + status.textContent = "✗ " + e; + status.style.color = "var(--err)"; + } + }); } // ── Settings Modal ── -var _updateTag=''; -var _updateUrl=''; -function openSettings(){ - fetch(_apiUrl('/api/settings')).then(function(r){return r.json()}).then(function(d){ - document.getElementById('s-printer-name').value=d.printer_name||''; - document.getElementById('s-printer-ip').value=d.printer_ip||''; - document.getElementById('s-mqtt-port').value=d.mqtt_port||9883; - document.getElementById('s-username').value=d.username||''; - document.getElementById('s-password').value=d.password||''; - document.getElementById('s-device-id').value=d.device_id||''; - document.getElementById('s-mode-id').value=d.mode_id||''; - document.getElementById('s-default-slot').value=d.default_ams_slot||'auto'; - document.getElementById('s-auto-leveling').checked=(d.auto_leveling===undefined?true:!!d.auto_leveling); - var cop=document.getElementById('s-camera-on-print');if(cop)cop.checked=!!d.camera_on_print; - var frm=document.getElementById('s-file-ready-mode');if(frm)frm.value=(d.print_start_dialog===undefined?'1':String(d.print_start_dialog?1:0)); - var wuw=document.getElementById('s-web-upload-warning');if(wuw)wuw.checked=(d.web_upload_warning===undefined?true:!!d.web_upload_warning); - notifRenderList(d.notifications||[]); - var enm=document.getElementById('s-notif-every-min');if(enm)enm.value=d.notify_every_minutes||0; - var enl=document.getElementById('s-notif-every-layers');if(enl)enl.value=d.notify_every_layers||0; - // Poll-Intervall (Sekunden) — Backend hat Vorrang vor localStorage - var pi=document.getElementById('s-poll-interval'); - if(pi){ - var sec=d.poll_interval||Math.round((parseInt(localStorage.getItem('pollInterval')||'2000'))/1000)||3; - pi.value=sec; - } - renderFilamentMapping(d.filament_profiles||{}); - }); +var _updateTag = ""; +var _updateUrl = ""; +function openSettings() { + fetch(_apiUrl("/api/settings")) + .then(function (r) { + return r.json(); + }) + .then(function (d) { + document.getElementById("s-printer-name").value = d.printer_name || ""; + document.getElementById("s-printer-ip").value = d.printer_ip || ""; + document.getElementById("s-mqtt-port").value = d.mqtt_port || 9883; + document.getElementById("s-username").value = d.username || ""; + document.getElementById("s-password").value = d.password || ""; + document.getElementById("s-device-id").value = d.device_id || ""; + document.getElementById("s-mode-id").value = d.mode_id || ""; + document.getElementById("s-default-slot").value = + d.default_ams_slot || "auto"; + document.getElementById("s-auto-leveling").checked = + d.auto_leveling === undefined ? true : !!d.auto_leveling; + var cop = document.getElementById("s-camera-on-print"); + if (cop) cop.checked = !!d.camera_on_print; + var frm = document.getElementById("s-file-ready-mode"); + if (frm) + frm.value = + d.print_start_dialog === undefined + ? "1" + : String(d.print_start_dialog ? 1 : 0); + var wuw = document.getElementById("s-web-upload-warning"); + if (wuw) + wuw.checked = + d.web_upload_warning === undefined ? true : !!d.web_upload_warning; + notifRenderList(d.notifications || []); + var enm = document.getElementById("s-notif-every-min"); + if (enm) enm.value = d.notify_every_minutes || 0; + var enl = document.getElementById("s-notif-every-layers"); + if (enl) enl.value = d.notify_every_layers || 0; + // Poll-Intervall (Sekunden) — Backend hat Vorrang vor localStorage + var pi = document.getElementById("s-poll-interval"); + if (pi) { + var sec = + d.poll_interval || + Math.round( + parseInt(localStorage.getItem("pollInterval") || "2000") / 1000, + ) || + 3; + pi.value = sec; + } + renderFilamentMapping(d.filament_profiles || {}); + }); // Sprach-Select im Settings-Panel mit aktueller Sprache spiegeln - var ls=document.getElementById('s-lang-select'); - if(ls)ls.value=(localStorage.getItem('lang')||document.documentElement.lang||'de'); - document.getElementById('s-version-label').textContent='v'+('__VERSION__'||'?'); - document.getElementById('update-status').textContent=''; - document.getElementById('btn-update-apply').style.display='none'; - var cl=document.getElementById('update-changelog');if(cl)cl.style.display='none'; - _updateTag='';_updateUrl=''; + var ls = document.getElementById("s-lang-select"); + if (ls) + ls.value = + localStorage.getItem("lang") || document.documentElement.lang || "de"; + document.getElementById("s-version-label").textContent = + "v" + ("__VERSION__" || "?"); + document.getElementById("update-status").textContent = ""; + document.getElementById("btn-update-apply").style.display = "none"; + var cl = document.getElementById("update-changelog"); + if (cl) cl.style.display = "none"; + _updateTag = ""; + _updateUrl = ""; // Custom-Profile-Liste laden (Issue #41) refreshUserProfileList(); // Vendor-Sichtbarkeitsfilter (Issue #41 Option A) loadVendorChecklist(); } -function closeSettings(){ +function closeSettings() { // Panel-Variante: zurück zum Dashboard - showPanel('dashboard'); + showPanel("dashboard"); } // Poll-Intervall-Feld → Live-Poll sofort übernehmen (Persistenz erst beim Speichern) -function onPollIntervalInput(){ - var pi=document.getElementById('s-poll-interval'); - if(!pi)return; - var sec=parseInt(pi.value,10); - if(sec>=1&&sec<=60)setPoll(sec*1000); +function onPollIntervalInput() { + var pi = document.getElementById("s-poll-interval"); + if (!pi) return; + var sec = parseInt(pi.value, 10); + if (sec >= 1 && sec <= 60) setPoll(sec * 1000); } // ── Filament-Profil-Mapping pro Slot ([filament_profiles]) ── @@ -1137,1994 +1799,3263 @@ function onPollIntervalInput(){ // 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; - var rows=''; - for(var i=0;i<4;i++){ - var m=map[i]||map[String(i)]||{}; - var idHint=m.id?' ('+m.id+')':''; - rows+=''; +function renderFilamentMapping(map) { + var el = document.getElementById("filament-mapping-list"); + if (!el) return; + var rows = ""; + for (var i = 0; i < 4; i++) { + var m = map[i] || map[String(i)] || {}; + var idHint = m.id + ? ' (' + m.id + ")" + : ""; + rows += + '"; } - el.innerHTML=rows; + el.innerHTML = rows; // Dropdowns befüllen (async, geteilter Profil-Cache + Vendor-Filter) - for(var j=0;j<4;j++){ _fillMappingDropdown(j); } + 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=''; - 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; +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 = + '"; + 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);}); + 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];}); + _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); + 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(){ +function saveFilamentMapping() { // Nutzt den per-Slot-Endpoint (vendor,name → ID-Lookup im Backend). // Leere Auswahl ("") = 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)||''; - chain=chain.then(function(){ - return fetch(_apiUrl('/kx/filament/slots/'+slot+'/profile'), - {method:'POST',headers:{'Content-Type':'application/json'}, - body:JSON.stringify({vendor:vendor,name:name})}); + 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) || ""; + chain = chain.then(function () { + return fetch(_apiUrl("/kx/filament/slots/" + slot + "/profile"), { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ vendor: vendor, name: name }), + }); }); })(i); } - chain.then(function(){ - clog(tr('log_filament_mapping_saved')||'Filament-Mapping gespeichert','msg-ok'); - openSettings(); // neu laden → ID-Hints aktualisieren - }).catch(function(e){clog('Mapping-Fehler: '+e,'msg-err');}); + chain + .then(function () { + clog( + tr("log_filament_mapping_saved") || "Filament-Mapping gespeichert", + "msg-ok", + ); + openSettings(); // neu laden → ID-Hints aktualisieren + }) + .catch(function (e) { + clog("Mapping-Fehler: " + e, "msg-err"); + }); } // ── Vendor-Sichtbarkeitsfilter (Issue #41 Option A) ── -var _vendorChecklistSel={}; // {vendor:true} — laufende Auswahl im UI -function loadVendorChecklist(){ +var _vendorChecklistSel = {}; // {vendor:true} — laufende Auswahl im UI +function loadVendorChecklist() { // aktuelle Auswahl aus Backend, dann alle verfügbaren Vendoren rendern - _visibleVendors=null; // Cache invalidieren - _loadVisibleVendors(function(vis){ - _vendorChecklistSel={}; - (vis||[]).forEach(function(v){_vendorChecklistSel[v]=true;}); + _visibleVendors = null; // Cache invalidieren + _loadVisibleVendors(function (vis) { + _vendorChecklistSel = {}; + (vis || []).forEach(function (v) { + _vendorChecklistSel[v] = true; + }); renderVendorChecklist(); }); } -function renderVendorChecklist(){ - var el=document.getElementById('visible-vendors-list'); - if(!el)return; - _loadOrcaFilaments(function(profiles){ +function renderVendorChecklist() { + var el = document.getElementById("visible-vendors-list"); + if (!el) return; + _loadOrcaFilaments(function (profiles) { // alle System-Vendoren (ohne Generic — der ist immer sichtbar) sammeln - var set={}; - profiles.forEach(function(p){ if(!p.is_user && p.vendor && p.vendor!=='Generic') set[p.vendor]=1; }); - var vendors=Object.keys(set).sort(); - var q=((document.getElementById('vendor-filter-search')||{}).value||'').toLowerCase(); - if(q)vendors=vendors.filter(function(v){return v.toLowerCase().indexOf(q)>=0;}); - el.innerHTML=vendors.map(function(v){ - var ck=_vendorChecklistSel[v]?'checked':''; - var safe=v.replace(/"/g,'"'); - return ''; - }).join('')||''; + var set = {}; + profiles.forEach(function (p) { + if (!p.is_user && p.vendor && p.vendor !== "Generic") set[p.vendor] = 1; + }); + var vendors = Object.keys(set).sort(); + var q = ( + (document.getElementById("vendor-filter-search") || {}).value || "" + ).toLowerCase(); + if (q) + vendors = vendors.filter(function (v) { + return v.toLowerCase().indexOf(q) >= 0; + }); + el.innerHTML = + vendors + .map(function (v) { + var ck = _vendorChecklistSel[v] ? "checked" : ""; + var safe = v.replace(/"/g, """); + return ( + '" + ); + }) + .join("") || ''; }); } -function _vendorCheck(cb){ - var v=cb.getAttribute('data-vendor'); - if(cb.checked)_vendorChecklistSel[v]=true; else delete _vendorChecklistSel[v]; +function _vendorCheck(cb) { + var v = cb.getAttribute("data-vendor"); + if (cb.checked) _vendorChecklistSel[v] = true; + else delete _vendorChecklistSel[v]; } -function saveVisibleVendors(){ - var vendors=Object.keys(_vendorChecklistSel); - fetch(_apiUrl('/kx/filament/visible_vendors'),{method:'POST',headers:{'Content-Type':'application/json'}, - body:JSON.stringify({vendors:vendors})}).then(function(r){return r.json();}).then(function(){ - _visibleVendors=vendors.slice(); // Dropdown-Cache aktualisieren - clog(tr('log_visible_vendors_saved')||'Hersteller-Auswahl gespeichert','msg-ok'); - }).catch(function(e){clog('Vendor-Filter-Fehler: '+e,'msg-err');}); +function saveVisibleVendors() { + var vendors = Object.keys(_vendorChecklistSel); + fetch(_apiUrl("/kx/filament/visible_vendors"), { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ vendors: vendors }), + }) + .then(function (r) { + return r.json(); + }) + .then(function () { + _visibleVendors = vendors.slice(); // Dropdown-Cache aktualisieren + clog( + tr("log_visible_vendors_saved") || "Hersteller-Auswahl gespeichert", + "msg-ok", + ); + }) + .catch(function (e) { + clog("Vendor-Filter-Fehler: " + e, "msg-err"); + }); } // ── Custom Filament Profile Import (Issue #41) ── -function refreshUserProfileList(){ - var listEl=document.getElementById('orca-profiles-list'); - if(!listEl) return; - fetch(_apiUrl('/kx/filament/profiles/user')).then(function(r){return r.json();}).then(function(d){ - var profs=(d && d.result)||[]; - if(!profs.length){ - listEl.innerHTML=''+(tr('orca_profile_user_empty')||'– keine –')+''; - return; - } - listEl.innerHTML=profs.map(function(p){ - var label=p.vendor+' / '+p.name+' ('+p.type+')'; - return '
' - +'★ '+label+'' - +'' - +'
'; - }).join(''); - }).catch(function(){}); +function refreshUserProfileList() { + var listEl = document.getElementById("orca-profiles-list"); + if (!listEl) return; + fetch(_apiUrl("/kx/filament/profiles/user")) + .then(function (r) { + return r.json(); + }) + .then(function (d) { + var profs = (d && d.result) || []; + if (!profs.length) { + listEl.innerHTML = + '' + + (tr("orca_profile_user_empty") || "– keine –") + + ""; + return; + } + listEl.innerHTML = profs + .map(function (p) { + var label = p.vendor + " / " + p.name + " (" + p.type + ")"; + return ( + '
' + + "★ " + + label + + "" + + "' + + "
" + ); + }) + .join(""); + }) + .catch(function () {}); } -function deleteUserProfile(vendor, name){ - fetch(_apiUrl('/kx/filament/profiles/user?vendor='+vendor+'&name='+name), {method:'DELETE'}) - .then(function(r){return r.json();}) - .then(function(){ - _orcaFilamentCache=null; +function deleteUserProfile(vendor, name) { + fetch( + _apiUrl("/kx/filament/profiles/user?vendor=" + vendor + "&name=" + name), + { method: "DELETE" }, + ) + .then(function (r) { + return r.json(); + }) + .then(function () { + _orcaFilamentCache = null; refreshUserProfileList(); // Falls Import-Dialog offen ist, dort auch refreshen refreshImportDialogList(); }); } -function openProfileImport(){ - document.getElementById('profile-import-status').textContent=''; +function openProfileImport() { + document.getElementById("profile-import-status").textContent = ""; refreshImportDialogList(); - document.getElementById('profile-import-modal').classList.add('open'); + document.getElementById("profile-import-modal").classList.add("open"); } -function closeProfileImport(){ - document.getElementById('profile-import-modal').classList.remove('open'); +function closeProfileImport() { + document.getElementById("profile-import-modal").classList.remove("open"); } -function refreshImportDialogList(){ - var el=document.getElementById('profile-import-list'); - if(!el) return; - fetch(_apiUrl('/kx/filament/profiles/user')).then(function(r){return r.json();}).then(function(d){ - var profs=(d && d.result)||[]; - if(!profs.length){ - el.innerHTML=''+(tr('orca_profile_user_empty')||'– keine –')+''; - return; - } - el.innerHTML=profs.map(function(p){ - var label=p.vendor+' / '+p.name+' ('+p.type+')'; - return '
' - +'★ '+label+'' - +'' - +'
'; - }).join(''); - }).catch(function(){}); +function refreshImportDialogList() { + var el = document.getElementById("profile-import-list"); + if (!el) return; + fetch(_apiUrl("/kx/filament/profiles/user")) + .then(function (r) { + return r.json(); + }) + .then(function (d) { + var profs = (d && d.result) || []; + if (!profs.length) { + el.innerHTML = + '' + + (tr("orca_profile_user_empty") || "– keine –") + + ""; + return; + } + el.innerHTML = profs + .map(function (p) { + var label = p.vendor + " / " + p.name + " (" + p.type + ")"; + return ( + '
' + + "★ " + + label + + "" + + "' + + "
" + ); + }) + .join(""); + }) + .catch(function () {}); } -function doProfileImportUpload(files){ - if(!files || !files.length) return; - var status=document.getElementById('profile-import-status'); - status.textContent=(tr('orca_profile_uploading')||'Lade hoch…'); - status.style.color='var(--txt2)'; - var done=0, totalAdded=0, totalSkipped=0; - function _one(idx){ - if(idx>=files.length){ - status.textContent=(tr('orca_profile_done')||'Importiert')+': '+totalAdded - +(totalSkipped?' / '+totalSkipped+' '+(tr('orca_profile_skipped')||'übersprungen'):''); - status.style.color='var(--ok)'; - _orcaFilamentCache=null; +function doProfileImportUpload(files) { + if (!files || !files.length) return; + var status = document.getElementById("profile-import-status"); + status.textContent = tr("orca_profile_uploading") || "Lade hoch…"; + status.style.color = "var(--txt2)"; + var done = 0, + totalAdded = 0, + totalSkipped = 0; + function _one(idx) { + if (idx >= files.length) { + status.textContent = + (tr("orca_profile_done") || "Importiert") + + ": " + + totalAdded + + (totalSkipped + ? " / " + + totalSkipped + + " " + + (tr("orca_profile_skipped") || "übersprungen") + : ""); + status.style.color = "var(--ok)"; + _orcaFilamentCache = null; refreshImportDialogList(); refreshUserProfileList(); // Vendor-Checkliste neu aufbauen — ein Import kann einen bisher // unbekannten System-Vendor mitbringen (Issue #41). - if(document.getElementById('visible-vendors-list')) renderVendorChecklist(); + if (document.getElementById("visible-vendors-list")) + renderVendorChecklist(); // Wenn Slot-Edit offen ist, Dropdown gleich neu befüllen - var mat=document.getElementById('slot-edit-mat'); - if(mat && document.getElementById('slot-edit-modal').classList.contains('open')){ - _fillSlotProfileDropdown(mat.value, '', ''); + var mat = document.getElementById("slot-edit-mat"); + if ( + mat && + document.getElementById("slot-edit-modal").classList.contains("open") + ) { + _fillSlotProfileDropdown(mat.value, "", ""); } return; } - var fd=new FormData(); - fd.append('file', files[idx]); - fetch(_apiUrl('/kx/filament/profiles/user'), {method:'POST', body:fd}) - .then(function(r){return r.json();}) - .then(function(d){ - totalAdded += (d.added||0); - totalSkipped += (d.skipped||0); - done++; - _one(idx+1); + var fd = new FormData(); + fd.append("file", files[idx]); + fetch(_apiUrl("/kx/filament/profiles/user"), { method: "POST", body: fd }) + .then(function (r) { + return r.json(); }) - .catch(function(e){ - status.textContent=tr('log_error','Fehler:')+' '+e; - status.style.color='var(--err)'; + .then(function (d) { + totalAdded += d.added || 0; + totalSkipped += d.skipped || 0; + done++; + _one(idx + 1); + }) + .catch(function (e) { + status.textContent = tr("log_error", "Fehler:") + " " + e; + status.style.color = "var(--err)"; }); } _one(0); } // ── AMS Slot Edit ── -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; - if(_slotEditIndex<0){ - btn.style.display='none'; +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; + if (_slotEditIndex < 0) { + btn.style.display = "none"; return; } - btn.style.display=''; - btn.textContent=_slotEditLoaded?tr('slot_edit_unload'):tr('slot_edit_load'); + btn.style.display = ""; + btn.textContent = _slotEditLoaded + ? tr("slot_edit_unload") + : tr("slot_edit_load"); } -var _orcaFilamentCache=null; // [{id,name,vendor,type,color}, …] -var _visibleVendors=null; // Vendor-Sichtbarkeitsfilter (Issue #41); [] = alle -function _loadVisibleVendors(cb){ - if(_visibleVendors!==null){ cb(_visibleVendors); return; } - fetch(_apiUrl('/kx/filament/visible_vendors')).then(function(r){return r.json();}).then(function(d){ - _visibleVendors=d.result||[]; +var _orcaFilamentCache = null; // [{id,name,vendor,type,color}, …] +var _visibleVendors = null; // Vendor-Sichtbarkeitsfilter (Issue #41); [] = alle +function _loadVisibleVendors(cb) { + if (_visibleVendors !== null) { cb(_visibleVendors); - }).catch(function(){ _visibleVendors=[]; cb([]); }); + return; + } + fetch(_apiUrl("/kx/filament/visible_vendors")) + .then(function (r) { + return r.json(); + }) + .then(function (d) { + _visibleVendors = d.result || []; + cb(_visibleVendors); + }) + .catch(function () { + _visibleVendors = []; + cb([]); + }); } -function _loadOrcaFilaments(cb){ - if(_orcaFilamentCache){ cb(_orcaFilamentCache); return; } - fetch(_apiUrl('/kx/filament/profiles')).then(function(r){return r.json();}).then(function(d){ - _orcaFilamentCache=d.result||[]; +function _loadOrcaFilaments(cb) { + if (_orcaFilamentCache) { cb(_orcaFilamentCache); - }).catch(function(){ cb([]); }); + return; + } + fetch(_apiUrl("/kx/filament/profiles")) + .then(function (r) { + return r.json(); + }) + .then(function (d) { + _orcaFilamentCache = d.result || []; + cb(_orcaFilamentCache); + }) + .catch(function () { + cb([]); + }); } -function _profileKey(vendor, name){ +function _profileKey(vendor, name) { // Eindeutiger Selector: (vendor, name). IDs aus orca_filaments.json sind // NICHT eindeutig (z.B. 136 Profile mit OGFL99). Wir kodieren beide in den // '; + sel.innerHTML = + '"; // User-Profile (is_user) zuerst — eigene Optgroup '★ Eigene' an erster Stelle. - var userProfs=matched.filter(function(p){return p.is_user;}); - var systemProfs=matched.filter(function(p){return !p.is_user;}); - function _appendOption(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; - if(o.value===wantKey) o.selected=true; + var userProfs = matched.filter(function (p) { + return p.is_user; + }); + var systemProfs = matched.filter(function (p) { + return !p.is_user; + }); + function _appendOption(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; + 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){ _appendOption(gUser, p); }); + if (userProfs.length) { + var gUser = document.createElement("optgroup"); + gUser.label = "★ " + (tr("orca_profile_user_label") || "Eigene Profile"); + userProfs.forEach(function (p) { + _appendOption(gUser, p); + }); sel.appendChild(gUser); } // Vendor-Sichtbarkeitsfilter (Issue #41 Option A): nur gewählte Vendoren + // Generic. Leere Liste = alle (rückwärtskompatibel). Eigene Profile (is_user) // sind oben bereits unkonditional drin. - _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];}); + _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){ _appendOption(g, p); }); - sel.appendChild(g); + 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) { + _appendOption(g, p); + }); + sel.appendChild(g); + }); }); }); } -function openSlotEdit(i){ - var slot=(window._amsSlots||[])[i]||{}; - var globalIdx=slot.global_index!=null?slot.global_index:(slot.index!=null?slot.index:i); - _slotEditIndex=globalIdx; - _slotEditLoaded=(S.ams_loaded_slot!=null&&S.ams_loaded_slot===globalIdx); - document.getElementById('slot-edit-title').textContent=T.slot_edit_title+' '+(globalIdx+1); - var rgb=Array.isArray(slot.color)?slot.color:[128,128,128]; - var hex='#'+rgb.map(function(v){return('0'+Math.min(255,v).toString(16)).slice(-2)}).join(''); - var ci=document.getElementById('slot-edit-color'); - ci.value=hex; - document.getElementById('slot-edit-preview').style.background=hex; - var mat=(slot.type||'PLA').toUpperCase(); - document.getElementById('slot-edit-mat').value=mat; - var btns=document.getElementById('slot-mat-btns'); - btns.innerHTML=_MAT_PRESETS.map(function(m){ - return ''; - }).join(''); +function openSlotEdit(i) { + var slot = (window._amsSlots || [])[i] || {}; + var globalIdx = + slot.global_index != null + ? slot.global_index + : slot.index != null + ? slot.index + : i; + _slotEditIndex = globalIdx; + _slotEditLoaded = + S.ams_loaded_slot != null && S.ams_loaded_slot === globalIdx; + document.getElementById("slot-edit-title").textContent = + T.slot_edit_title + " " + (globalIdx + 1); + var rgb = Array.isArray(slot.color) ? slot.color : [128, 128, 128]; + var hex = + "#" + + rgb + .map(function (v) { + return ("0" + Math.min(255, v).toString(16)).slice(-2); + }) + .join(""); + var ci = document.getElementById("slot-edit-color"); + ci.value = hex; + document.getElementById("slot-edit-preview").style.background = hex; + var mat = (slot.type || "PLA").toUpperCase(); + document.getElementById("slot-edit-mat").value = mat; + var btns = document.getElementById("slot-mat-btns"); + btns.innerHTML = _MAT_PRESETS + .map(function (m) { + return ( + '" + ); + }) + .join(""); // OrcaSlicer-Profil-Dropdown: aktuellen User-Override für diesen Slot // aus /kx/filament/slots holen (enthält vendor+name+id). - fetch(_apiUrl('/kx/filament/slots')).then(function(r){return r.json();}).then(function(d){ - var arr=d.result||[]; - var entry=arr.find(function(x){return x.slot_index===globalIdx;})||{}; - window._slotProfileMap=window._slotProfileMap||{}; - window._slotProfileMap[globalIdx]={ - id: entry.filament_id||'', - vendor:entry.filament_vendor||'', - name: entry.filament_name||'', - }; - _fillSlotProfileDropdown(mat, entry.filament_vendor||'', entry.filament_name||''); - }).catch(function(){ _fillSlotProfileDropdown(mat,'',''); }); + fetch(_apiUrl("/kx/filament/slots")) + .then(function (r) { + return r.json(); + }) + .then(function (d) { + var arr = d.result || []; + var entry = + arr.find(function (x) { + return x.slot_index === globalIdx; + }) || {}; + window._slotProfileMap = window._slotProfileMap || {}; + window._slotProfileMap[globalIdx] = { + id: entry.filament_id || "", + vendor: entry.filament_vendor || "", + name: entry.filament_name || "", + }; + _fillSlotProfileDropdown( + mat, + entry.filament_vendor || "", + entry.filament_name || "", + ); + }) + .catch(function () { + _fillSlotProfileDropdown(mat, "", ""); + }); updateSlotEditFeedButton(); - document.getElementById('slot-edit-modal').classList.add('open'); + document.getElementById("slot-edit-modal").classList.add("open"); } -function closeSlotEdit(){ - _slotEditIndex=-1; - document.getElementById('slot-edit-modal').classList.remove('open'); +function closeSlotEdit() { + _slotEditIndex = -1; + document.getElementById("slot-edit-modal").classList.remove("open"); } -function slotEditFeed(){ - if(_slotEditIndex<0)return; - var type=_slotEditLoaded?2:1; - amsFeed(type,_slotEditIndex) - .then(function(){ - _slotEditLoaded=!_slotEditLoaded; +function slotEditFeed() { + if (_slotEditIndex < 0) return; + var type = _slotEditLoaded ? 2 : 1; + amsFeed(type, _slotEditIndex) + .then(function () { + _slotEditLoaded = !_slotEditLoaded; updateSlotEditFeedButton(); poll(); }) - .catch(function(){}); + .catch(function () {}); } -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);} +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(); }) - .catch(function(e){ - clog(tr('log_error','Fehler:')+' '+e,'msg-err'); - if(btn){btn.disabled=false;setText('file-ready-btn',T.file_ready_btn);} + .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", "Fehler:") + " " + 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); }); + 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){ + var currentFile = (storeFiles || []).find(function (f) { + return f.filename === fn; + }); + if (currentFile) { _gateAndStart(currentFile); 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(); - }); + 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(); + }); } -function cancelReadyFile(){ - post('/api/file_ready/clear',{}) - .then(function(){document.getElementById('file-ready-banner').style.display='none';}); +function cancelReadyFile() { + post("/api/file_ready/clear", {}).then(function () { + document.getElementById("file-ready-banner").style.display = "none"; + }); } // ── Aktionen für geladene/idle Datei in der Progress-Karte (Issue #55) ── -function startIdleFile(){ - if(_lastLoadedFile) startReadyFile(_lastLoadedFile); +function startIdleFile() { + if (_lastLoadedFile) startReadyFile(_lastLoadedFile); } -function startIdleFileWithSlots(){ - if(_lastLoadedFile) startReadyFileWithSlots(_lastLoadedFile); +function startIdleFileWithSlots() { + if (_lastLoadedFile) startReadyFileWithSlots(_lastLoadedFile); } -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 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){ - document.getElementById('slot-edit-mat').value=m; +function selectMatPreset(m) { + document.getElementById("slot-edit-mat").value = m; highlightMatBtn(m); // Filament-Profile-Dropdown an neues Material anpassen // (vorherige Selektion zurücksetzen — andere Material-Profile passen nicht) - _fillSlotProfileDropdown(m, '', ''); + _fillSlotProfileDropdown(m, "", ""); } -function highlightMatBtn(val){ - document.querySelectorAll('.mat-preset-btn').forEach(function(b){ - var on=b.getAttribute('data-mat')===val.toUpperCase(); - b.style.background=on?'var(--accent)':'var(--raised)'; - b.style.color=on?'#fff':'var(--txt2)'; +function highlightMatBtn(val) { + document.querySelectorAll(".mat-preset-btn").forEach(function (b) { + var on = b.getAttribute("data-mat") === val.toUpperCase(); + b.style.background = on ? "var(--accent)" : "var(--raised)"; + b.style.color = on ? "#fff" : "var(--txt2)"; }); // Auch bei manueller Eingabe ins Material-Textfeld: Dropdown refreshen. - if(val) _fillSlotProfileDropdown(val, '', ''); + if (val) _fillSlotProfileDropdown(val, "", ""); } -function hexToRgb(hex){ - var r=parseInt(hex.slice(1,3),16),g=parseInt(hex.slice(3,5),16),b=parseInt(hex.slice(5,7),16); - return[r,g,b]; +function hexToRgb(hex) { + var r = parseInt(hex.slice(1, 3), 16), + g = parseInt(hex.slice(3, 5), 16), + b = parseInt(hex.slice(5, 7), 16); + return [r, g, b]; } -function saveSlotEdit(){ - var hex=document.getElementById('slot-edit-color').value; - var mat=document.getElementById('slot-edit-mat').value.trim().toUpperCase()||'PLA'; - var color=hexToRgb(hex); - var slotIdx=_slotEditIndex; - var profSel=document.getElementById('slot-edit-profile'); - var sel=profSel && profSel.selectedOptions && profSel.selectedOptions[0]; +function saveSlotEdit() { + var hex = document.getElementById("slot-edit-color").value; + var mat = + document.getElementById("slot-edit-mat").value.trim().toUpperCase() || + "PLA"; + var color = hexToRgb(hex); + var slotIdx = _slotEditIndex; + var profSel = document.getElementById("slot-edit-profile"); + var sel = profSel && profSel.selectedOptions && profSel.selectedOptions[0]; // Primärer Selector: (vendor, name). id ist nur Hint (aus dem JSON-data-attr // mitgegeben — Backend lookt sie nochmal selber nach um veraltete Hints zu // korrigieren). - var newProfVendor=sel?(sel.dataset.vendor||''):''; - var newProfName =sel?(sel.dataset.name ||''):''; - var newProfId =sel?(sel.dataset.id ||''):''; + var newProfVendor = sel ? sel.dataset.vendor || "" : ""; + var newProfName = sel ? sel.dataset.name || "" : ""; + var newProfId = sel ? sel.dataset.id || "" : ""; // Sequenziell speichern: erst Profil-Override (config.ini), dann Material/Farbe // (MQTT zum Drucker). Sonst können beide Pfade sich überholen und der Slot-State // ist beim nächsten Re-Open inkonsistent. - fetch(_apiUrl('/kx/filament/slots/'+slotIdx+'/profile'),{ - method:'POST', - headers:{'Content-Type':'application/json'}, - body:JSON.stringify({vendor:newProfVendor, name:newProfName, id:newProfId}) + fetch(_apiUrl("/kx/filament/slots/" + slotIdx + "/profile"), { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + vendor: newProfVendor, + name: newProfName, + id: newProfId, + }), }) - .then(function(r){return r.json();}) - .then(function(){ - window._slotProfileMap=window._slotProfileMap||{}; - if(newProfVendor && newProfName){ - window._slotProfileMap[slotIdx]={id:newProfId, vendor:newProfVendor, name:newProfName}; - } else { - delete window._slotProfileMap[slotIdx]; - } - return post('/api/ams/set_slot',{index:slotIdx,type:mat,color:color}); - }) - .then(function(r){return r?r.json():null;}) - .then(function(){ - // Slot-Map refreshen damit die Karte sofort den Vendor zeigt. - return fetch(_apiUrl('/kx/filament/slots')).then(function(r){return r.json();}); - }) - .then(function(d){ - var arr=(d && d.result)||[]; - window._slotProfileMap={}; - arr.forEach(function(e){ - if(e.filament_vendor && e.filament_name){ - window._slotProfileMap[e.slot_index]={ - id: e.filament_id||'', - vendor:e.filament_vendor, - name: e.filament_name, + .then(function (r) { + return r.json(); + }) + .then(function () { + window._slotProfileMap = window._slotProfileMap || {}; + if (newProfVendor && newProfName) { + window._slotProfileMap[slotIdx] = { + id: newProfId, + vendor: newProfVendor, + name: newProfName, }; + } else { + delete window._slotProfileMap[slotIdx]; + } + return post("/api/ams/set_slot", { + index: slotIdx, + type: mat, + color: color, + }); + }) + .then(function (r) { + return r ? r.json() : null; + }) + .then(function () { + // Slot-Map refreshen damit die Karte sofort den Vendor zeigt. + return fetch(_apiUrl("/kx/filament/slots")).then(function (r) { + return r.json(); + }); + }) + .then(function (d) { + var arr = (d && d.result) || []; + window._slotProfileMap = {}; + arr.forEach(function (e) { + if (e.filament_vendor && e.filament_name) { + window._slotProfileMap[e.slot_index] = { + id: e.filament_id || "", + vendor: e.filament_vendor, + name: e.filament_name, + }; + } + }); + closeSlotEdit(); + var profSuffix = newProfName + ? " [" + newProfVendor + " " + newProfName + "]" + : ""; + clog( + tr("slot_edit_ok") + + " " + + (slotIdx + 1) + + ": " + + mat + + " " + + hex + + profSuffix, + "msg-ok", + ); + // Sofortiges Re-Render mit aktuellem _slotProfileMap (poll() ist async + // und re-rendert beim nächsten Tick — wir wollen aber dass die Vendor- + // Badge JETZT direkt sichtbar wird). + if (typeof applyState === "function") applyState(); + if (typeof poll === "function") poll(); + }) + .catch(function (e) { + clog(tr("log_error", "Fehler:") + " " + e, "msg-err"); + }); +} +document.addEventListener("DOMContentLoaded", function () { + document + .getElementById("s-printer-ip") + .addEventListener("input", function () { + var hint = document.getElementById("lbl-ip-hint"); + if (this.value.includes(":")) { + hint.textContent = T.hint_ip_no_port; + hint.style.display = "block"; + } else { + hint.style.display = "none"; } }); - closeSlotEdit(); - var profSuffix=newProfName?(' ['+newProfVendor+' '+newProfName+']'):''; - clog(tr('slot_edit_ok')+' '+(slotIdx+1)+': '+mat+' '+hex+profSuffix,'msg-ok'); - // Sofortiges Re-Render mit aktuellem _slotProfileMap (poll() ist async - // und re-rendert beim nächsten Tick — wir wollen aber dass die Vendor- - // Badge JETZT direkt sichtbar wird). - if(typeof applyState==='function') applyState(); - if(typeof poll==='function') poll(); - }) - .catch(function(e){clog(tr('log_error','Fehler:')+' '+e,'msg-err');}); -} -document.addEventListener('DOMContentLoaded',function(){ - document.getElementById('s-printer-ip').addEventListener('input',function(){ - var hint=document.getElementById('lbl-ip-hint'); - if(this.value.includes(':')){hint.textContent=T.hint_ip_no_port;hint.style.display='block';} - else{hint.style.display='none';} - }); }); -function setPoll(ms){ - localStorage.setItem('pollInterval',ms); +function setPoll(ms) { + localStorage.setItem("pollInterval", ms); clearInterval(pollTimer); - pollTimer=setInterval(poll,ms); + pollTimer = setInterval(poll, ms); } -function saveSettings(){ - var btn=document.getElementById('btn-save-settings'); - btn.disabled=true;btn.textContent='…'; - var webUploadWarning=(document.getElementById('s-web-upload-warning')||{}).checked?1:0; - S.web_upload_warning=webUploadWarning; +function saveSettings() { + var btn = document.getElementById("btn-save-settings"); + 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, - mqtt_port: parseInt(document.getElementById('s-mqtt-port').value)||9883, - username: document.getElementById('s-username').value, - password: document.getElementById('s-password').value, - device_id: document.getElementById('s-device-id').value, - mode_id: document.getElementById('s-mode-id').value, - default_ams_slot: document.getElementById('s-default-slot').value, - auto_leveling: document.getElementById('s-auto-leveling').checked?1:0, - camera_on_print: (document.getElementById('s-camera-on-print')||{}).checked?1:0, - print_start_dialog: parseInt((document.getElementById('s-file-ready-mode')||{}).value||'1',10), - web_upload_warning:webUploadWarning, - poll_interval: Math.min(60,Math.max(1,parseInt((document.getElementById('s-poll-interval')||{}).value,10)||3)), - notifications: notifCollect(), - notify_every_minutes: parseInt((document.getElementById('s-notif-every-min')||{}).value||'0',10)||0, - notify_every_layers: parseInt((document.getElementById('s-notif-every-layers')||{}).value||'0',10)||0, - }).then(function(){ - btn.textContent=T.update_restarting; - setTimeout(function(){ - btn.disabled=false; - setText('btn-save-settings',T.settings_save); + _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, + mqtt_port: parseInt(document.getElementById("s-mqtt-port").value) || 9883, + username: document.getElementById("s-username").value, + password: document.getElementById("s-password").value, + device_id: document.getElementById("s-device-id").value, + mode_id: document.getElementById("s-mode-id").value, + default_ams_slot: document.getElementById("s-default-slot").value, + auto_leveling: document.getElementById("s-auto-leveling").checked ? 1 : 0, + camera_on_print: (document.getElementById("s-camera-on-print") || {}) + .checked + ? 1 + : 0, + print_start_dialog: parseInt( + (document.getElementById("s-file-ready-mode") || {}).value || "1", + 10, + ), + web_upload_warning: webUploadWarning, + poll_interval: Math.min( + 60, + Math.max( + 1, + parseInt( + (document.getElementById("s-poll-interval") || {}).value, + 10, + ) || 3, + ), + ), + notifications: notifCollect(), + notify_every_minutes: + parseInt( + (document.getElementById("s-notif-every-min") || {}).value || "0", + 10, + ) || 0, + notify_every_layers: + parseInt( + (document.getElementById("s-notif-every-layers") || {}).value || "0", + 10, + ) || 0, + }) + .then(function () { + btn.textContent = T.update_restarting; + setTimeout(function () { + btn.disabled = false; + setText("btn-save-settings", T.settings_save); + closeSettings(); + poll(); + }, 4000); + }) + .catch(function (e) { + btn.disabled = false; + setText("btn-save-settings", T.settings_save); + clog( + T.settings_title + " " + tr("log_error", "Fehler:") + " " + e, + "msg-err", + ); + }); +} +function checkUpdate() { + var sb = document.getElementById("update-status"); + sb.textContent = T.update_checking; + document.getElementById("btn-update-apply").style.display = "none"; + _updateTag = ""; + _updateUrl = ""; + fetch(_apiUrl("/api/update/check")) + .then(function (r) { + return r.json(); + }) + .then(function (d) { + if (d.error) { + sb.textContent = T.update_error + ": " + d.error; + return; + } + var cl = document.getElementById("update-changelog"); + if (d.changelog && d.changelog.trim()) { + cl.textContent = d.changelog; + cl.style.display = "block"; + } else { + cl.style.display = "none"; + } + if (d.update_available) { + sb.textContent = "v" + d.latest + " " + T.update_available; + sb.style.color = "var(--ok)"; + _updateTag = d.tag; + _updateUrl = d.download_url; + document.getElementById("btn-update-apply").style.display = + "inline-block"; + } else { + sb.textContent = T.update_none; + sb.style.color = "var(--txt2)"; + } + }) + .catch(function (e) { + sb.textContent = T.update_error + ": " + e; + }); +} +function applyUpdate() { + if (!_updateUrl) return; + var sb = document.getElementById("update-status"); + var btn = document.getElementById("btn-update-apply"); + btn.disabled = true; + sb.textContent = T.update_applying; + post("/api/update/apply", { download_url: _updateUrl, tag: _updateTag }) + .then(function () { + sb.textContent = T.update_restarting; closeSettings(); - poll(); - },4000); - }).catch(function(e){ - btn.disabled=false;setText('btn-save-settings',T.settings_save); - clog(T.settings_title+' '+tr('log_error','Fehler:')+' '+e,'msg-err'); - }); -} -function checkUpdate(){ - var sb=document.getElementById('update-status'); - sb.textContent=T.update_checking; - document.getElementById('btn-update-apply').style.display='none'; - _updateTag='';_updateUrl=''; - fetch(_apiUrl('/api/update/check')).then(function(r){return r.json()}).then(function(d){ - if(d.error){sb.textContent=T.update_error+': '+d.error;return;} - var cl=document.getElementById('update-changelog'); - if(d.changelog&&d.changelog.trim()){cl.textContent=d.changelog;cl.style.display='block';} - else{cl.style.display='none';} - if(d.update_available){ - sb.textContent='v'+d.latest+' '+T.update_available; - sb.style.color='var(--ok)'; - _updateTag=d.tag;_updateUrl=d.download_url; - document.getElementById('btn-update-apply').style.display='inline-block'; - } else { - sb.textContent=T.update_none; - sb.style.color='var(--txt2)'; - } - }).catch(function(e){sb.textContent=T.update_error+': '+e;}); -} -function applyUpdate(){ - if(!_updateUrl)return; - var sb=document.getElementById('update-status'); - var btn=document.getElementById('btn-update-apply'); - btn.disabled=true;sb.textContent=T.update_applying; - post('/api/update/apply',{download_url:_updateUrl,tag:_updateTag}).then(function(){ - sb.textContent=T.update_restarting; - closeSettings(); - setTimeout(function(){poll();},5000); - }).catch(function(e){ - btn.disabled=false;sb.textContent=T.update_error+': '+e; - }); + setTimeout(function () { + poll(); + }, 5000); + }) + .catch(function (e) { + btn.disabled = false; + sb.textContent = T.update_error + ": " + e; + }); } // ── Poll ── -async function poll(){ - try{ - var r=await fetch(_apiUrl('/api/state')); - if(!r.ok)return; - var d=await r.json(); - Object.assign(S,d); +async function poll() { + try { + var r = await fetch(_apiUrl("/api/state")); + if (!r.ok) return; + var d = await r.json(); + Object.assign(S, d); applyState(); updateHistory(); - }catch(e){clog(T.log_poll_error+' '+e,'msg-err')} + } catch (e) { + clog(T.log_poll_error + " " + e, "msg-err"); + } } var pollTimer; -(function(){ - var ms=parseInt(localStorage.getItem('pollInterval')||'2000'); +(function () { + var ms = parseInt(localStorage.getItem("pollInterval") || "2000"); initPrinters(); // Slot-Profile-Map initial laden, sonst zeigen die Karten beim ersten // Render keine Vendor-Badge obwohl in der config.ini ein Override steht. - fetch(_apiUrl('/kx/filament/slots')).then(function(r){return r.json();}).then(function(d){ - var arr=(d && d.result)||[]; - window._slotProfileMap={}; - arr.forEach(function(e){ - if(e.filament_vendor && e.filament_name){ - window._slotProfileMap[e.slot_index]={ - id: e.filament_id||'', - vendor:e.filament_vendor, - name: e.filament_name, - }; - } - }); - }).catch(function(){}); - poll();pollTimer=setInterval(poll,ms); + fetch(_apiUrl("/kx/filament/slots")) + .then(function (r) { + return r.json(); + }) + .then(function (d) { + var arr = (d && d.result) || []; + window._slotProfileMap = {}; + arr.forEach(function (e) { + if (e.filament_vendor && e.filament_name) { + window._slotProfileMap[e.slot_index] = { + id: e.filament_id || "", + vendor: e.filament_vendor, + name: e.filament_name, + }; + } + }); + }) + .catch(function () {}); + poll(); + pollTimer = setInterval(poll, ms); })(); // ── Print actions ── -function printAction(a){ - post('/printer/print/'+a,{}).then(function(){clog(tr('log_print_action','Druck:')+' '+a,'msg-ok');poll()}) - .catch(function(e){clog(tr('log_error','Fehler:')+' '+e,'msg-err')}); +function printAction(a) { + post("/printer/print/" + a, {}) + .then(function () { + clog(tr("log_print_action", "Druck:") + " " + a, "msg-ok"); + poll(); + }) + .catch(function (e) { + clog(tr("log_error", "Fehler:") + " " + e, "msg-err"); + }); } -function togglePauseResume(){ +function togglePauseResume() { // Druckt → pause; Pausiert → resume. Status kommt aus dem zuletzt gepollten // print_state in S; bei Unklarheit (kein State) Pause als Default. - var state=(S && S.print_state)||''; - if(state==='paused') printAction('resume'); - else printAction('pause'); + var state = (S && S.print_state) || ""; + if (state === "paused") printAction("resume"); + else printAction("pause"); } -function updatePauseResumeBtn(){ - var btn=document.getElementById('d-btn-pause'); - if(!btn) return; - var state=(S && S.print_state)||''; - if(state==='paused'){ - btn.textContent=T.btn_resume||'▶ Weiter'; - btn.classList.add('btn-resume'); - btn.classList.remove('btn-pause'); +function updatePauseResumeBtn() { + var btn = document.getElementById("d-btn-pause"); + if (!btn) return; + var state = (S && S.print_state) || ""; + if (state === "paused") { + btn.textContent = T.btn_resume || "▶ Weiter"; + btn.classList.add("btn-resume"); + btn.classList.remove("btn-pause"); } else { - btn.textContent=T.btn_pause||'⏸ Pause'; - btn.classList.add('btn-pause'); - btn.classList.remove('btn-resume'); + btn.textContent = T.btn_pause || "⏸ Pause"; + btn.classList.add("btn-pause"); + btn.classList.remove("btn-resume"); } } -function confirmCancel(){if(confirm(T.confirm_cancel||'Druck wirklich abbrechen?'))printAction('cancel')} +function confirmCancel() { + if (confirm(T.confirm_cancel || "Druck wirklich abbrechen?")) + printAction("cancel"); +} // ── Axis motion ── // axis codes: 0=X, 1=Y, 2=Z // move_type 1=relative, distance positive/negative -function getStep(){return currentStep} -function setStep(btn,v){ - currentStep=v; - document.querySelectorAll('.step-btn').forEach(b=>b.classList.remove('active')); - btn.classList.add('active'); - document.getElementById('step-display').textContent=v; +function getStep() { + return currentStep; } -function move(axis,dir,dist){ +function setStep(btn, v) { + currentStep = v; + document + .querySelectorAll(".step-btn") + .forEach((b) => b.classList.remove("active")); + btn.classList.add("active"); + document.getElementById("step-display").textContent = v; +} +function move(axis, dir, dist) { // axis: 0=X,1=Y,2=Z → printer axis codes: 1=X,2=Y,3=Z - var axisMap={0:1,1:2,2:3}; - post('/api/axis',{axis:axisMap[axis],move_type:1,distance:dir*dist}) - .then(function(){clog(tr('log_axis','Achse')+' '+(axis===0?'X':axis===1?'Y':'Z')+' '+(dir>0?'+':'')+dir*dist+'mm','msg-ok')}) - .catch(function(e){clog(tr('log_axis','Achse')+'-'+tr('log_error','Fehler:')+' '+e,'msg-err')}); + var axisMap = { 0: 1, 1: 2, 2: 3 }; + post("/api/axis", { axis: axisMap[axis], move_type: 1, distance: dir * dist }) + .then(function () { + clog( + tr("log_axis", "Achse") + + " " + + (axis === 0 ? "X" : axis === 1 ? "Y" : "Z") + + " " + + (dir > 0 ? "+" : "") + + dir * dist + + "mm", + "msg-ok", + ); + }) + .catch(function (e) { + clog( + tr("log_axis", "Achse") + "-" + tr("log_error", "Fehler:") + " " + e, + "msg-err", + ); + }); } -function homeAll(){ - post('/api/axis',{axis:5,move_type:2,distance:0}) - .then(function(){clog(tr('log_home_all','Home All'),'msg-ok')}) - .catch(function(e){clog(tr('log_home_all','Home All')+' '+tr('log_error','Fehler:')+' '+e,'msg-err')}); +function homeAll() { + post("/api/axis", { axis: 5, move_type: 2, distance: 0 }) + .then(function () { + clog(tr("log_home_all", "Home All"), "msg-ok"); + }) + .catch(function (e) { + clog( + tr("log_home_all", "Home All") + + " " + + tr("log_error", "Fehler:") + + " " + + e, + "msg-err", + ); + }); } -function homeXY(){ - post('/api/axis',{axis:4,move_type:2,distance:0}) - .then(function(){clog(tr('btn_home_xy','Home XY'),'msg-ok')}) - .catch(function(e){clog(tr('btn_home_xy','Home XY')+' '+tr('log_error','Fehler:')+' '+e,'msg-err')}); +function homeXY() { + post("/api/axis", { axis: 4, move_type: 2, distance: 0 }) + .then(function () { + clog(tr("btn_home_xy", "Home XY"), "msg-ok"); + }) + .catch(function (e) { + clog( + tr("btn_home_xy", "Home XY") + + " " + + tr("log_error", "Fehler:") + + " " + + e, + "msg-err", + ); + }); } -function homeZ(){ - post('/api/axis',{axis:3,move_type:2,distance:0}) - .then(function(){clog(tr('btn_home_z','Home Z'),'msg-ok')}) - .catch(function(e){clog(tr('btn_home_z','Home Z')+' '+tr('log_error','Fehler:')+' '+e,'msg-err')}); +function homeZ() { + post("/api/axis", { axis: 3, move_type: 2, distance: 0 }) + .then(function () { + clog(tr("btn_home_z", "Home Z"), "msg-ok"); + }) + .catch(function (e) { + clog( + tr("btn_home_z", "Home Z") + " " + tr("log_error", "Fehler:") + " " + e, + "msg-err", + ); + }); } -function disableMotors(){ - post('/api/axis',{action:'turnOff'}) - .then(function(){clog(tr('btn_disable_motors','Motors Off'),'msg-ok')}) - .catch(function(e){clog(tr('btn_disable_motors','Motors Off')+' '+tr('log_error','Fehler:')+' '+e,'msg-err')}); +function disableMotors() { + post("/api/axis", { action: "turnOff" }) + .then(function () { + clog(tr("btn_disable_motors", "Motors Off"), "msg-ok"); + }) + .catch(function (e) { + clog( + tr("btn_disable_motors", "Motors Off") + + " " + + tr("log_error", "Fehler:") + + " " + + e, + "msg-err", + ); + }); } // ── Temperature ── -function setNozzle(){ - var v=parseFloat(document.getElementById('p-nozzle-inp').value||0); - post('/api/temperature',{nozzle:v,bed:S.bed_target}) - .then(function(){clog(tr('log_nozzle','Nozzle → ')+v+'°C','msg-ok')}) - .catch(function(e){clog(tr('label_nozzle','Düse')+' '+tr('log_error','Fehler:')+' '+e,'msg-err')}); +function setNozzle() { + var v = parseFloat(document.getElementById("p-nozzle-inp").value || 0); + post("/api/temperature", { nozzle: v, bed: S.bed_target }) + .then(function () { + clog(tr("log_nozzle", "Nozzle → ") + v + "°C", "msg-ok"); + }) + .catch(function (e) { + clog( + tr("label_nozzle", "Düse") + " " + tr("log_error", "Fehler:") + " " + e, + "msg-err", + ); + }); } -function setBed(){ - var v=parseFloat(document.getElementById('p-bed-inp').value||0); - post('/api/temperature',{nozzle:S.nozzle_target,bed:v}) - .then(function(){clog(tr('log_bed','Bett → ')+v+'°C','msg-ok')}) - .catch(function(e){clog(tr('label_bed','Bett')+' '+tr('log_error','Fehler:')+' '+e,'msg-err')}); +function setBed() { + var v = parseFloat(document.getElementById("p-bed-inp").value || 0); + post("/api/temperature", { nozzle: S.nozzle_target, bed: v }) + .then(function () { + clog(tr("log_bed", "Bett → ") + v + "°C", "msg-ok"); + }) + .catch(function (e) { + clog( + tr("label_bed", "Bett") + " " + tr("log_error", "Fehler:") + " " + e, + "msg-err", + ); + }); } // ── Light ── -function setLight(){ - var on=document.getElementById('d-light-toggle').checked; - post('/api/light',{on:on,brightness:80}) - .then(function(){clog('Licht '+(on?'an, 80%':'aus'),'msg-ok')}) +function setLight() { + var on = document.getElementById("d-light-toggle").checked; + post("/api/light", { on: on, brightness: 80 }) + .then(function () { + clog("Licht " + (on ? "an, 80%" : "aus"), "msg-ok"); + }) - .catch(function(e){clog('Licht-Fehler: '+e,'msg-err')}); + .catch(function (e) { + clog("Licht-Fehler: " + e, "msg-err"); + }); } // ── Print Speed ── -function setSpeed(mode){ - S.print_speed_mode=mode; - [1,2,3].forEach(function(m){ - var b=document.getElementById('d-spd-'+m); - if(b) b.classList.toggle('spd-active',m===mode); +function setSpeed(mode) { + S.print_speed_mode = mode; + [1, 2, 3].forEach(function (m) { + var b = document.getElementById("d-spd-" + m); + if (b) b.classList.toggle("spd-active", m === mode); + }); + post("/api/speed", { mode: mode }).catch(function (e) { + clog( + tr("label_speed", "Geschwindigkeit") + + " " + + tr("log_error", "Fehler:") + + " " + + e, + "msg-err", + ); }); - post('/api/speed',{mode:mode}) - .catch(function(e){clog(tr('label_speed','Geschwindigkeit')+' '+tr('log_error','Fehler:')+' '+e,'msg-err')}); } // ── Fan ── -function setFan(){ - var v=parseInt(document.getElementById('d-fan').value); - document.getElementById('d-fan-val').textContent=v; - post('/api/fan',{speed:v}) - .then(function(){clog(tr('log_fan','Lüfter → ')+v+'%','msg-ok')}) - .catch(function(e){clog(tr('log_fan','Lüfter → ')+tr('log_error','Fehler:')+' '+e,'msg-err')}); +function setFan() { + var v = parseInt(document.getElementById("d-fan").value); + document.getElementById("d-fan-val").textContent = v; + post("/api/fan", { speed: v }) + .then(function () { + clog(tr("log_fan", "Lüfter → ") + v + "%", "msg-ok"); + }) + .catch(function (e) { + clog( + tr("log_fan", "Lüfter → ") + tr("log_error", "Fehler:") + " " + e, + "msg-err", + ); + }); } -function quickFan(v){ - document.getElementById('d-fan').value=v; - document.getElementById('d-fan-val').textContent=v; - post('/api/fan',{speed:v}) - .then(function(){clog(tr('log_fan','Lüfter → ')+v+'%','msg-ok')}) - .catch(function(e){clog(tr('log_fan','Lüfter → ')+tr('log_error','Fehler:')+' '+e,'msg-err')}); +function quickFan(v) { + document.getElementById("d-fan").value = v; + document.getElementById("d-fan-val").textContent = v; + post("/api/fan", { speed: v }) + .then(function () { + clog(tr("log_fan", "Lüfter → ") + v + "%", "msg-ok"); + }) + .catch(function (e) { + clog( + tr("log_fan", "Lüfter → ") + tr("log_error", "Fehler:") + " " + e, + "msg-err", + ); + }); } // ── AMS ── -function amsFeed(type,slotIndex){ +function amsFeed(type, slotIndex) { var globalIdx; - if(typeof slotIndex==='number'&&slotIndex>=0){ - globalIdx=slotIndex; - }else{ - var i=parseInt(document.getElementById('ams-slot-sel').value); - var slot=(window._amsSlots||[])[i]||{}; - globalIdx=slot.global_index!=null?slot.global_index:i; + if (typeof slotIndex === "number" && slotIndex >= 0) { + globalIdx = slotIndex; + } else { + var i = parseInt(document.getElementById("ams-slot-sel").value); + var slot = (window._amsSlots || [])[i] || {}; + globalIdx = slot.global_index != null ? slot.global_index : i; } - return post('/api/ams/feed',{slot_index:globalIdx,type:type}) - .then(function(){clog((type===1?T.lbl_feed:T.lbl_unload)+' Slot '+(globalIdx+1),'msg-ok')}) - .catch(function(e){clog('AMS '+tr('log_error','Fehler:')+' '+e,'msg-err');throw e;}); + return post("/api/ams/feed", { slot_index: globalIdx, type: type }) + .then(function () { + clog( + (type === 1 ? T.lbl_feed : T.lbl_unload) + " Slot " + (globalIdx + 1), + "msg-ok", + ); + }) + .catch(function (e) { + clog("AMS " + tr("log_error", "Fehler:") + " " + e, "msg-err"); + throw e; + }); } // ── Camera ── -function camStart(){ - var img=document.getElementById('cam-img'); - var ph=document.getElementById('cam-placeholder'); - var sp=document.getElementById('cam-spinner'); - ph.style.display='none'; - img.style.display='none'; - sp.style.display='block'; - post('/api/camera/start',{}).then(function(){ - camOn=true; - document.getElementById('cam-toggle-btn').textContent=tr('btn_cam_stop'); - clog(tr('log_cam_start'),'msg-ok'); - setTimeout(function(){ sp.style.display='none'; img.style.display='block'; },1200); - if(/Android/i.test(navigator.userAgent)){ - // Android browsers don't support multipart/x-mixed-replace (MJPEG) — poll snapshots instead - _camPollInterval=setInterval(function(){ img.src='/api/camera/snapshot?t='+Date.now(); },200); - } else { - img.onerror=function(){ - sp.style.display='none'; - img.style.display='none'; - ph.style.display='flex'; - camOn=false; - document.getElementById('cam-toggle-btn').textContent=tr('btn_cam_start'); - clog(tr('log_error','Fehler:')+' '+tr('cam_stream_unavailable'),'msg-err'); - }; - img.src='/api/camera/stream?t='+Date.now(); - } - }).catch(function(e){ - sp.style.display='none'; - ph.style.display='flex'; - clog(tr('log_error','Fehler:')+' '+e,'msg-err'); - }); +function camStart() { + var img = document.getElementById("cam-img"); + var ph = document.getElementById("cam-placeholder"); + var sp = document.getElementById("cam-spinner"); + ph.style.display = "none"; + img.style.display = "none"; + sp.style.display = "block"; + post("/api/camera/start", {}) + .then(function () { + camOn = true; + document.getElementById("cam-toggle-btn").textContent = + tr("btn_cam_stop"); + clog(tr("log_cam_start"), "msg-ok"); + setTimeout(function () { + sp.style.display = "none"; + img.style.display = "block"; + }, 1200); + if (/Android/i.test(navigator.userAgent)) { + // Android browsers don't support multipart/x-mixed-replace (MJPEG) — poll snapshots instead + _camPollInterval = setInterval(function () { + img.src = "/api/camera/snapshot?t=" + Date.now(); + }, 200); + } else { + img.onerror = function () { + sp.style.display = "none"; + img.style.display = "none"; + ph.style.display = "flex"; + camOn = false; + document.getElementById("cam-toggle-btn").textContent = + tr("btn_cam_start"); + clog( + tr("log_error", "Fehler:") + " " + tr("cam_stream_unavailable"), + "msg-err", + ); + }; + img.src = "/api/camera/stream?t=" + Date.now(); + } + }) + .catch(function (e) { + sp.style.display = "none"; + ph.style.display = "flex"; + clog(tr("log_error", "Fehler:") + " " + e, "msg-err"); + }); } -function camStop(){ - var img=document.getElementById('cam-img'); - if(_camPollInterval){clearInterval(_camPollInterval);_camPollInterval=null;} - img.onerror=null; // deregister error handler before clearing src to avoid spurious error toast - post('/api/camera/stop',{}).catch(function(){}); - img.src=''; - img.style.display='none'; - document.getElementById('cam-placeholder').style.display='flex'; - camOn=false; - camUserStopped=true; // suppress auto-restart for remainder of this print - document.getElementById('cam-toggle-btn').textContent=tr('btn_cam_start'); - clog(tr('log_cam_stop'),'msg-ok'); +function camStop() { + var img = document.getElementById("cam-img"); + if (_camPollInterval) { + clearInterval(_camPollInterval); + _camPollInterval = null; + } + img.onerror = null; // deregister error handler before clearing src to avoid spurious error toast + post("/api/camera/stop", {}).catch(function () {}); + img.src = ""; + img.style.display = "none"; + document.getElementById("cam-placeholder").style.display = "flex"; + camOn = false; + camUserStopped = true; // suppress auto-restart for remainder of this print + document.getElementById("cam-toggle-btn").textContent = tr("btn_cam_start"); + clog(tr("log_cam_stop"), "msg-ok"); } -function aceDryStart(aceId){ - aceId=(typeof aceId==='number'&&aceId>=0)?aceId:0; - var prof=_aceDryProfileGet(aceId); - var t=parseInt(prof.temp,10); - var d=_aceDryDurationMinFromSec(prof.duration_sec); - t=Math.max(30,Math.min(80,t)); - d=Math.max(10,Math.min(1440,d)); - return post('/api/ace/dry',{action:'start',target_temp:t,duration:d,ace_id:aceId}) - .then(function(r){return r.json();}) - .then(function(r){ - if(r.error){throw new Error(r.error);} - clog('ACE '+(aceId+1)+' - '+tr('ace_dry_dryer')+': '+tr('ace_dry_start')+' ('+t+'°C, '+d+' min)','msg-ok'); +function aceDryStart(aceId) { + aceId = typeof aceId === "number" && aceId >= 0 ? aceId : 0; + var prof = _aceDryProfileGet(aceId); + var t = parseInt(prof.temp, 10); + var d = _aceDryDurationMinFromSec(prof.duration_sec); + t = Math.max(30, Math.min(80, t)); + d = Math.max(10, Math.min(1440, d)); + return post("/api/ace/dry", { + action: "start", + target_temp: t, + duration: d, + ace_id: aceId, + }) + .then(function (r) { + return r.json(); + }) + .then(function (r) { + if (r.error) { + throw new Error(r.error); + } + clog( + "ACE " + + (aceId + 1) + + " - " + + tr("ace_dry_dryer") + + ": " + + tr("ace_dry_start") + + " (" + + t + + "°C, " + + d + + " min)", + "msg-ok", + ); poll(); }) - .catch(function(e){clog('ACE '+tr('log_error','Fehler:')+' '+e,'msg-err');}); + .catch(function (e) { + clog("ACE " + tr("log_error", "Fehler:") + " " + e, "msg-err"); + }); } -var _aceAutoFeedPending={}; -function aceAutoRefillToggle(aceId){ - aceId=(typeof aceId==='number'&&aceId>=0)?aceId:0; - var on=!!((document.getElementById('ace-auto-refill-toggle-'+aceId)||{}).checked); - _aceAutoFeedPending[aceId]=true; - fetch('/api/ace/auto_feed',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({ace_id:aceId,on:on?1:0})}) - .then(function(r){return r.json();}) - .then(function(d){ - delete _aceAutoFeedPending[aceId]; - if(d.error){clog('Auto Refill error: '+d.error,'msg-err');var t=document.getElementById('ace-auto-refill-toggle-'+aceId);if(t)t.checked=!on;return;} - clog('ACE '+(aceId+1)+' - '+tr('ace_dry_auto_refill')+': '+(on?'ON':'OFF'),'msg-ok'); +var _aceAutoFeedPending = {}; +function aceAutoRefillToggle(aceId) { + aceId = typeof aceId === "number" && aceId >= 0 ? aceId : 0; + var on = !!(document.getElementById("ace-auto-refill-toggle-" + aceId) || {}) + .checked; + _aceAutoFeedPending[aceId] = true; + fetch("/api/ace/auto_feed", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ ace_id: aceId, on: on ? 1 : 0 }), + }) + .then(function (r) { + return r.json(); }) - .catch(function(e){delete _aceAutoFeedPending[aceId];clog('Auto Refill error: '+e,'msg-err');var t=document.getElementById('ace-auto-refill-toggle-'+aceId);if(t)t.checked=!on;}); + .then(function (d) { + delete _aceAutoFeedPending[aceId]; + if (d.error) { + clog("Auto Refill error: " + d.error, "msg-err"); + var t = document.getElementById("ace-auto-refill-toggle-" + aceId); + if (t) t.checked = !on; + return; + } + clog( + "ACE " + + (aceId + 1) + + " - " + + tr("ace_dry_auto_refill") + + ": " + + (on ? "ON" : "OFF"), + "msg-ok", + ); + }) + .catch(function (e) { + delete _aceAutoFeedPending[aceId]; + clog("Auto Refill error: " + e, "msg-err"); + var t = document.getElementById("ace-auto-refill-toggle-" + aceId); + if (t) t.checked = !on; + }); } -function openAceDryDialog(aceId){ - aceId=(typeof aceId==='number'&&aceId>=0)?aceId:0; - _aceDryDialogAceId=aceId; +function openAceDryDialog(aceId) { + aceId = typeof aceId === "number" && aceId >= 0 ? aceId : 0; + _aceDryDialogAceId = aceId; _syncAceDryPresetsFromServer(S.ace_dry_presets); - _aceDryDialogPresetOriginals=JSON.parse(JSON.stringify(ACE_DRY_PRESETS)); + _aceDryDialogPresetOriginals = JSON.parse(JSON.stringify(ACE_DRY_PRESETS)); aceDryDialogSyncCustomButtonNames(); - var hasStored=Object.prototype.hasOwnProperty.call(aceDryProfiles,String(aceId)); - var prof=_aceDryProfileGet(aceId); - if(hasStored&&prof.preset&&ACE_DRY_PRESETS[prof.preset]){ + var hasStored = Object.prototype.hasOwnProperty.call( + aceDryProfiles, + String(aceId), + ); + var prof = _aceDryProfileGet(aceId); + if (hasStored && prof.preset && ACE_DRY_PRESETS[prof.preset]) { aceDryDialogPreset(prof.preset); - }else if(hasStored){ - var sec=prof.duration_sec; - document.getElementById('ace-dry-dialog-temp').value=prof.temp; - document.getElementById('ace-dry-dialog-h').value=Math.floor(sec/3600); - document.getElementById('ace-dry-dialog-m').value=Math.floor((sec%3600)/60); - document.getElementById('ace-dry-dialog-s').value=sec%60; - aceDryDialogHighlightPreset(''); - }else{ - aceDryDialogPreset('pla'); + } else if (hasStored) { + var sec = prof.duration_sec; + document.getElementById("ace-dry-dialog-temp").value = prof.temp; + document.getElementById("ace-dry-dialog-h").value = Math.floor(sec / 3600); + document.getElementById("ace-dry-dialog-m").value = Math.floor( + (sec % 3600) / 60, + ); + document.getElementById("ace-dry-dialog-s").value = sec % 60; + aceDryDialogHighlightPreset(""); + } else { + aceDryDialogPreset("pla"); } aceDryDialogUpdateSaveButton(); aceDryDialogUpdateResetButton(); - var sb=document.getElementById('ace-dry-dialog-save-preset'); - if(sb){sb.disabled=false;sb.textContent=tr('ace_dry_dialog_save_restart');} - document.getElementById('ace-dry-dialog').classList.add('open'); + var sb = document.getElementById("ace-dry-dialog-save-preset"); + if (sb) { + sb.disabled = false; + sb.textContent = tr("ace_dry_dialog_save_restart"); + } + document.getElementById("ace-dry-dialog").classList.add("open"); } -function closeAceDryDialog(){ - _aceDryDialogAceId=-1; - _aceDryDialogPresetOriginals={}; - var sb=document.getElementById('ace-dry-dialog-save-preset'); - if(sb)sb.style.display='none'; - var rb=document.getElementById('ace-dry-dialog-reset-default'); - if(rb)rb.style.display='none'; - document.getElementById('ace-dry-dialog').classList.remove('open'); +function closeAceDryDialog() { + _aceDryDialogAceId = -1; + _aceDryDialogPresetOriginals = {}; + var sb = document.getElementById("ace-dry-dialog-save-preset"); + if (sb) sb.style.display = "none"; + var rb = document.getElementById("ace-dry-dialog-reset-default"); + if (rb) rb.style.display = "none"; + document.getElementById("ace-dry-dialog").classList.remove("open"); } -function aceDryDialogIsCustomPreset(key){ - return /^custom_[123]$/.test(String(key||'')); +function aceDryDialogIsCustomPreset(key) { + return /^custom_[123]$/.test(String(key || "")); } -function aceDryDialogSyncCustomButtonNames(){ - ['custom_1','custom_2','custom_3'].forEach(function(k){ - var b=document.querySelector('.ace-dry-preset-btn[data-preset="'+k+'"]'); - if(b)b.textContent=(ACE_DRY_PRESETS[k]&&ACE_DRY_PRESETS[k].name)||('Custom '+k.slice(-1)); +function aceDryDialogSyncCustomButtonNames() { + ["custom_1", "custom_2", "custom_3"].forEach(function (k) { + var b = document.querySelector( + '.ace-dry-preset-btn[data-preset="' + k + '"]', + ); + if (b) + b.textContent = + (ACE_DRY_PRESETS[k] && ACE_DRY_PRESETS[k].name) || + "Custom " + k.slice(-1); }); } -function aceDryDialogUpdateCustomNameUi(){ - var row=document.getElementById('ace-dry-dialog-custom-name-row'); - var input=document.getElementById('ace-dry-dialog-custom-name'); - if(!row||!input)return; - if(!aceDryDialogIsCustomPreset(_aceDryDialogPresetKey)){ - row.style.display='none'; +function aceDryDialogUpdateCustomNameUi() { + var row = document.getElementById("ace-dry-dialog-custom-name-row"); + var input = document.getElementById("ace-dry-dialog-custom-name"); + if (!row || !input) return; + if (!aceDryDialogIsCustomPreset(_aceDryDialogPresetKey)) { + row.style.display = "none"; return; } - row.style.display='flex'; - input.value=(ACE_DRY_PRESETS[_aceDryDialogPresetKey]&&ACE_DRY_PRESETS[_aceDryDialogPresetKey].name)||''; + row.style.display = "flex"; + input.value = + (ACE_DRY_PRESETS[_aceDryDialogPresetKey] && + ACE_DRY_PRESETS[_aceDryDialogPresetKey].name) || + ""; } -function aceDryDialogCurrentValues(){ - var t=parseInt(document.getElementById('ace-dry-dialog-temp').value||45,10); - var h=parseInt(document.getElementById('ace-dry-dialog-h').value||0,10); - var m=parseInt(document.getElementById('ace-dry-dialog-m').value||0,10); - var s=parseInt(document.getElementById('ace-dry-dialog-s').value||0,10); - t=Math.max(30,Math.min(80,t)); - h=Math.max(0,Math.min(24,h)); - m=Math.max(0,Math.min(59,m)); - s=Math.max(0,Math.min(59,s)); - var totalSec=(h*3600)+(m*60)+s; - totalSec=Math.max(10*60,Math.min(24*3600,totalSec)); - return {temp:t,duration_sec:totalSec}; +function aceDryDialogCurrentValues() { + var t = parseInt( + document.getElementById("ace-dry-dialog-temp").value || 45, + 10, + ); + var h = parseInt(document.getElementById("ace-dry-dialog-h").value || 0, 10); + var m = parseInt(document.getElementById("ace-dry-dialog-m").value || 0, 10); + var s = parseInt(document.getElementById("ace-dry-dialog-s").value || 0, 10); + t = Math.max(30, Math.min(80, t)); + h = Math.max(0, Math.min(24, h)); + m = Math.max(0, Math.min(59, m)); + s = Math.max(0, Math.min(59, s)); + var totalSec = h * 3600 + m * 60 + s; + totalSec = Math.max(10 * 60, Math.min(24 * 3600, totalSec)); + return { temp: t, duration_sec: totalSec }; } -function aceDryDialogUpdateSaveButton(){ - var btn=document.getElementById('ace-dry-dialog-save-preset'); - if(!btn)return; - var key=_aceDryDialogPresetKey||''; - if(!key||!ACE_DRY_PRESETS[key]){btn.style.display='none';return;} - var p=_aceDryDialogPresetOriginals[key]||ACE_DRY_PRESETS[key]; - var cur=aceDryDialogCurrentValues(); - var changed=(cur.temp!==Number(p.temp)||cur.duration_sec!==Number(p.duration_sec)); - if(aceDryDialogIsCustomPreset(key)){ - var nameInp=document.getElementById('ace-dry-dialog-custom-name'); - var n=((nameInp&&nameInp.value)||'').trim(); - var old=(p&&p.name?String(p.name):('Custom '+key.slice(-1))).trim(); - if((n||old)!==old)changed=true; +function aceDryDialogUpdateSaveButton() { + var btn = document.getElementById("ace-dry-dialog-save-preset"); + if (!btn) return; + var key = _aceDryDialogPresetKey || ""; + if (!key || !ACE_DRY_PRESETS[key]) { + btn.style.display = "none"; + return; } - btn.style.display=changed?'':'none'; + var p = _aceDryDialogPresetOriginals[key] || ACE_DRY_PRESETS[key]; + var cur = aceDryDialogCurrentValues(); + var changed = + cur.temp !== Number(p.temp) || cur.duration_sec !== Number(p.duration_sec); + if (aceDryDialogIsCustomPreset(key)) { + var nameInp = document.getElementById("ace-dry-dialog-custom-name"); + var n = ((nameInp && nameInp.value) || "").trim(); + var old = (p && p.name ? String(p.name) : "Custom " + key.slice(-1)).trim(); + if ((n || old) !== old) changed = true; + } + btn.style.display = changed ? "" : "none"; } -function aceDryDialogUpdateResetButton(){ - var btn=document.getElementById('ace-dry-dialog-reset-default'); - if(!btn)return; - var key=_aceDryDialogPresetKey||''; - var d=ACE_DRY_PRESET_DEFAULTS[key]; - if(!key||!d){btn.style.display='none';return;} - var cur=aceDryDialogCurrentValues(); - var changed=(cur.temp!==Number(d.temp)||cur.duration_sec!==Number(d.duration_sec)); - btn.style.display=changed?'':'none'; +function aceDryDialogUpdateResetButton() { + var btn = document.getElementById("ace-dry-dialog-reset-default"); + if (!btn) return; + var key = _aceDryDialogPresetKey || ""; + var d = ACE_DRY_PRESET_DEFAULTS[key]; + if (!key || !d) { + btn.style.display = "none"; + return; + } + var cur = aceDryDialogCurrentValues(); + var changed = + cur.temp !== Number(d.temp) || cur.duration_sec !== Number(d.duration_sec); + btn.style.display = changed ? "" : "none"; } -function aceDryDialogInputsChanged(){ - if(aceDryDialogIsCustomPreset(_aceDryDialogPresetKey)){ - var b=document.querySelector('.ace-dry-preset-btn[data-preset="'+_aceDryDialogPresetKey+'"]'); - var i=document.getElementById('ace-dry-dialog-custom-name'); - if(b&&i){ - var t=(i.value||'').trim(); - b.textContent=t||((ACE_DRY_PRESETS[_aceDryDialogPresetKey]&&ACE_DRY_PRESETS[_aceDryDialogPresetKey].name)||('Custom '+_aceDryDialogPresetKey.slice(-1))); +function aceDryDialogInputsChanged() { + if (aceDryDialogIsCustomPreset(_aceDryDialogPresetKey)) { + var b = document.querySelector( + '.ace-dry-preset-btn[data-preset="' + _aceDryDialogPresetKey + '"]', + ); + var i = document.getElementById("ace-dry-dialog-custom-name"); + if (b && i) { + var t = (i.value || "").trim(); + b.textContent = + t || + (ACE_DRY_PRESETS[_aceDryDialogPresetKey] && + ACE_DRY_PRESETS[_aceDryDialogPresetKey].name) || + "Custom " + _aceDryDialogPresetKey.slice(-1); } } aceDryDialogUpdateSaveButton(); aceDryDialogUpdateResetButton(); } -function aceDryDialogHighlightPreset(presetKey){ - _aceDryDialogPresetKey=presetKey||''; - document.querySelectorAll('.ace-dry-preset-btn').forEach(function(btn){ - var on=(btn.getAttribute('data-preset')===presetKey); - btn.style.background=on?'var(--accent)':'var(--raised)'; - btn.style.color=on?'#fff':'var(--txt2)'; - btn.style.borderColor=on?'var(--accent)':'var(--border)'; +function aceDryDialogHighlightPreset(presetKey) { + _aceDryDialogPresetKey = presetKey || ""; + document.querySelectorAll(".ace-dry-preset-btn").forEach(function (btn) { + var on = btn.getAttribute("data-preset") === presetKey; + btn.style.background = on ? "var(--accent)" : "var(--raised)"; + btn.style.color = on ? "#fff" : "var(--txt2)"; + btn.style.borderColor = on ? "var(--accent)" : "var(--border)"; }); aceDryDialogUpdateCustomNameUi(); } -function aceDryDialogPreset(presetKey){ - var p=ACE_DRY_PRESETS[presetKey]; - if(!p)return; - var sec=p.duration_sec; - document.getElementById('ace-dry-dialog-temp').value=p.temp; - document.getElementById('ace-dry-dialog-h').value=Math.floor(sec/3600); - document.getElementById('ace-dry-dialog-m').value=Math.floor((sec%3600)/60); - document.getElementById('ace-dry-dialog-s').value=sec%60; +function aceDryDialogPreset(presetKey) { + var p = ACE_DRY_PRESETS[presetKey]; + if (!p) return; + var sec = p.duration_sec; + document.getElementById("ace-dry-dialog-temp").value = p.temp; + document.getElementById("ace-dry-dialog-h").value = Math.floor(sec / 3600); + document.getElementById("ace-dry-dialog-m").value = Math.floor( + (sec % 3600) / 60, + ); + document.getElementById("ace-dry-dialog-s").value = sec % 60; aceDryDialogHighlightPreset(presetKey); aceDryDialogSyncCustomButtonNames(); aceDryDialogUpdateSaveButton(); aceDryDialogUpdateResetButton(); } -function resetAceDryPresetToDefault(){ - var key=_aceDryDialogPresetKey||''; - var d=ACE_DRY_PRESET_DEFAULTS[key]; - if(!key||!d)return; - var sec=Number(d.duration_sec)||0; - document.getElementById('ace-dry-dialog-temp').value=Number(d.temp)||45; - document.getElementById('ace-dry-dialog-h').value=Math.floor(sec/3600); - document.getElementById('ace-dry-dialog-m').value=Math.floor((sec%3600)/60); - document.getElementById('ace-dry-dialog-s').value=sec%60; +function resetAceDryPresetToDefault() { + var key = _aceDryDialogPresetKey || ""; + var d = ACE_DRY_PRESET_DEFAULTS[key]; + if (!key || !d) return; + var sec = Number(d.duration_sec) || 0; + document.getElementById("ace-dry-dialog-temp").value = Number(d.temp) || 45; + document.getElementById("ace-dry-dialog-h").value = Math.floor(sec / 3600); + document.getElementById("ace-dry-dialog-m").value = Math.floor( + (sec % 3600) / 60, + ); + document.getElementById("ace-dry-dialog-s").value = sec % 60; aceDryDialogInputsChanged(); } -function saveAceDryPresetAndRestart(){ - var key=_aceDryDialogPresetKey||''; - var btn=document.getElementById('ace-dry-dialog-save-preset'); - if(!key||!ACE_DRY_PRESETS[key]||!btn)return; - var cur=aceDryDialogCurrentValues(); - if(!ACE_DRY_PRESETS[key])ACE_DRY_PRESETS[key]={}; - ACE_DRY_PRESETS[key].temp=cur.temp; - ACE_DRY_PRESETS[key].duration_sec=cur.duration_sec; - if(aceDryDialogIsCustomPreset(key)){ - var nameInp=document.getElementById('ace-dry-dialog-custom-name'); - var nm=((nameInp&&nameInp.value)||'').trim(); - ACE_DRY_PRESETS[key].name=nm||('Custom '+key.slice(-1)); +function saveAceDryPresetAndRestart() { + var key = _aceDryDialogPresetKey || ""; + var btn = document.getElementById("ace-dry-dialog-save-preset"); + if (!key || !ACE_DRY_PRESETS[key] || !btn) return; + var cur = aceDryDialogCurrentValues(); + if (!ACE_DRY_PRESETS[key]) ACE_DRY_PRESETS[key] = {}; + ACE_DRY_PRESETS[key].temp = cur.temp; + ACE_DRY_PRESETS[key].duration_sec = cur.duration_sec; + if (aceDryDialogIsCustomPreset(key)) { + var nameInp = document.getElementById("ace-dry-dialog-custom-name"); + var nm = ((nameInp && nameInp.value) || "").trim(); + ACE_DRY_PRESETS[key].name = nm || "Custom " + key.slice(-1); } - btn.disabled=true; - btn.textContent='…'; - fetch(_apiUrl('/api/settings')).then(function(r){return r.json();}).then(function(d){ - d.ace_dry_presets={ - pla:{temp:ACE_DRY_PRESETS.pla.temp,duration_sec:ACE_DRY_PRESETS.pla.duration_sec}, - pla_plus:{temp:ACE_DRY_PRESETS.pla_plus.temp,duration_sec:ACE_DRY_PRESETS.pla_plus.duration_sec}, - petg:{temp:ACE_DRY_PRESETS.petg.temp,duration_sec:ACE_DRY_PRESETS.petg.duration_sec}, - tpu:{temp:ACE_DRY_PRESETS.tpu.temp,duration_sec:ACE_DRY_PRESETS.tpu.duration_sec}, - abs_asa:{temp:ACE_DRY_PRESETS.abs_asa.temp,duration_sec:ACE_DRY_PRESETS.abs_asa.duration_sec}, - pa_pc:{temp:ACE_DRY_PRESETS.pa_pc.temp,duration_sec:ACE_DRY_PRESETS.pa_pc.duration_sec}, - custom_1:{name:ACE_DRY_PRESETS.custom_1.name,temp:ACE_DRY_PRESETS.custom_1.temp,duration_sec:ACE_DRY_PRESETS.custom_1.duration_sec}, - custom_2:{name:ACE_DRY_PRESETS.custom_2.name,temp:ACE_DRY_PRESETS.custom_2.temp,duration_sec:ACE_DRY_PRESETS.custom_2.duration_sec}, - custom_3:{name:ACE_DRY_PRESETS.custom_3.name,temp:ACE_DRY_PRESETS.custom_3.temp,duration_sec:ACE_DRY_PRESETS.custom_3.duration_sec} - }; - return post('/api/settings',d); - }).then(function(){ - clog('ACE preset '+key+' '+tr('settings_save'),'msg-ok'); - closeAceDryDialog(); - }).catch(function(e){ - btn.disabled=false; - btn.textContent=tr('ace_dry_dialog_save_restart'); - clog('ACE preset '+tr('log_error','Fehler:')+' '+e,'msg-err'); - }); + btn.disabled = true; + btn.textContent = "…"; + fetch(_apiUrl("/api/settings")) + .then(function (r) { + return r.json(); + }) + .then(function (d) { + d.ace_dry_presets = { + pla: { + temp: ACE_DRY_PRESETS.pla.temp, + duration_sec: ACE_DRY_PRESETS.pla.duration_sec, + }, + pla_plus: { + temp: ACE_DRY_PRESETS.pla_plus.temp, + duration_sec: ACE_DRY_PRESETS.pla_plus.duration_sec, + }, + petg: { + temp: ACE_DRY_PRESETS.petg.temp, + duration_sec: ACE_DRY_PRESETS.petg.duration_sec, + }, + tpu: { + temp: ACE_DRY_PRESETS.tpu.temp, + duration_sec: ACE_DRY_PRESETS.tpu.duration_sec, + }, + abs_asa: { + temp: ACE_DRY_PRESETS.abs_asa.temp, + duration_sec: ACE_DRY_PRESETS.abs_asa.duration_sec, + }, + pa_pc: { + temp: ACE_DRY_PRESETS.pa_pc.temp, + duration_sec: ACE_DRY_PRESETS.pa_pc.duration_sec, + }, + custom_1: { + name: ACE_DRY_PRESETS.custom_1.name, + temp: ACE_DRY_PRESETS.custom_1.temp, + duration_sec: ACE_DRY_PRESETS.custom_1.duration_sec, + }, + custom_2: { + name: ACE_DRY_PRESETS.custom_2.name, + temp: ACE_DRY_PRESETS.custom_2.temp, + duration_sec: ACE_DRY_PRESETS.custom_2.duration_sec, + }, + custom_3: { + name: ACE_DRY_PRESETS.custom_3.name, + temp: ACE_DRY_PRESETS.custom_3.temp, + duration_sec: ACE_DRY_PRESETS.custom_3.duration_sec, + }, + }; + return post("/api/settings", d); + }) + .then(function () { + clog("ACE preset " + key + " " + tr("settings_save"), "msg-ok"); + closeAceDryDialog(); + }) + .catch(function (e) { + btn.disabled = false; + btn.textContent = tr("ace_dry_dialog_save_restart"); + clog("ACE preset " + tr("log_error", "Fehler:") + " " + e, "msg-err"); + }); } -function confirmAceDryDialog(){ - if(_aceDryDialogAceId<0)return; - var t=parseInt(document.getElementById('ace-dry-dialog-temp').value||45,10); - var h=parseInt(document.getElementById('ace-dry-dialog-h').value||0,10); - var m=parseInt(document.getElementById('ace-dry-dialog-m').value||0,10); - var s=parseInt(document.getElementById('ace-dry-dialog-s').value||0,10); - t=Math.max(30,Math.min(80,t)); - h=Math.max(0,Math.min(24,h)); - m=Math.max(0,Math.min(59,m)); - s=Math.max(0,Math.min(59,s)); - var totalSec=(h*3600)+(m*60)+s; - totalSec=Math.max(10*60,Math.min(24*3600,totalSec)); - var preset=_aceDryDialogPresetKey||''; - _aceDryProfileSet(_aceDryDialogAceId,t,totalSec,preset); +function confirmAceDryDialog() { + if (_aceDryDialogAceId < 0) return; + var t = parseInt( + document.getElementById("ace-dry-dialog-temp").value || 45, + 10, + ); + var h = parseInt(document.getElementById("ace-dry-dialog-h").value || 0, 10); + var m = parseInt(document.getElementById("ace-dry-dialog-m").value || 0, 10); + var s = parseInt(document.getElementById("ace-dry-dialog-s").value || 0, 10); + t = Math.max(30, Math.min(80, t)); + h = Math.max(0, Math.min(24, h)); + m = Math.max(0, Math.min(59, m)); + s = Math.max(0, Math.min(59, s)); + var totalSec = h * 3600 + m * 60 + s; + totalSec = Math.max(10 * 60, Math.min(24 * 3600, totalSec)); + var preset = _aceDryDialogPresetKey || ""; + _aceDryProfileSet(_aceDryDialogAceId, t, totalSec, preset); closeAceDryDialog(); applyState(); } -function aceDryToggle(aceId,on){ - if(on)return aceDryStart(aceId); +function aceDryToggle(aceId, on) { + if (on) return aceDryStart(aceId); return aceDryStop(aceId); } -function toggleCam(){if(camOn)camStop();else camStart()} - -function aceDryStop(aceId){ - aceId=(typeof aceId==='number'&&aceId>=0)?aceId:0; - return post('/api/ace/dry',{action:'stop',ace_id:aceId}) - .then(function(r){return r.json();}) - .then(function(r){ - if(r.error){throw new Error(r.error);} - clog('ACE '+(aceId+1)+' - '+tr('ace_dry_dryer')+': '+tr('ace_dry_stop'),'msg-ok'); - poll(); - }) - .catch(function(e){clog('ACE '+tr('log_error','Fehler:')+' '+e,'msg-err');}); +function toggleCam() { + if (camOn) camStop(); + else camStart(); } -function loadStore(){ - fetch(_apiUrl('/kx/files')).then(function(r){return r.json()}).then(function(d){ - storeFiles=d.result||[]; - renderStore(); - }).catch(function(e){clog(tr('log_error','Fehler:')+' '+e,'msg-err')}); -} - -function uploadGcode(file){ - if(!file) return; - var zone=document.getElementById('store-upload-zone'); - var status=document.getElementById('store-upload-status'); - var label=document.getElementById('store-upload-label'); - // Nur druckbare Dateien zulassen (Issue #59) — Drag&Drop umgeht das - // accept-Attribut, daher hier explizit prüfen. - var _fn=(file.name||'').toLowerCase(); - if(!/\.(gcode|gcode\.3mf|3mf|bgcode)$/.test(_fn)){ - if(status){ status.textContent=T.store_upload_only_gcode||'Only GCode files allowed'; status.style.display=''; status.className='upload-status-err'; } - clog('Upload abgelehnt (kein GCode): '+file.name,'msg-err'); - return; - } - if(status) { status.textContent=T.store_upload_busy; status.style.display=''; status.className='upload-status-busy'; } - if(label) label.style.display='none'; - if(zone) zone.style.pointerEvents='none'; - var fd=new FormData(); - fd.append('file', file); - fd.append('web_upload', 'true'); - fetch(_apiUrl('/api/files/local'),{method:'POST',body:fd}) - .then(function(r){ - if(!r.ok) return r.text().then(function(t){throw new Error(r.status+': '+t);}); +function aceDryStop(aceId) { + aceId = typeof aceId === "number" && aceId >= 0 ? aceId : 0; + return post("/api/ace/dry", { action: "stop", ace_id: aceId }) + .then(function (r) { return r.json(); }) - .then(function(){ - if(status){ status.textContent=T.store_upload_success.replace('{file}',file.name); status.className='upload-status-ok'; } - loadStore(); - setTimeout(function(){ - if(status){status.style.display='none'; status.className='';} - if(label) label.style.display=''; - if(zone) zone.style.pointerEvents=''; - }, 3000); + .then(function (r) { + if (r.error) { + throw new Error(r.error); + } + clog( + "ACE " + + (aceId + 1) + + " - " + + tr("ace_dry_dryer") + + ": " + + tr("ace_dry_stop"), + "msg-ok", + ); + poll(); }) - .catch(function(e){ - if(status){ status.textContent=T.store_upload_error.replace('{error}',e.message); status.className='upload-status-err'; } - if(label) label.style.display=''; - if(zone) zone.style.pointerEvents=''; - clog(tr('log_error','Fehler:')+' '+e,'msg-err'); + .catch(function (e) { + clog("ACE " + tr("log_error", "Fehler:") + " " + e, "msg-err"); }); } -function renderStore(){ - var grid=document.getElementById('store-grid'); - var empty=document.getElementById('store-empty'); +function loadStore() { + fetch(_apiUrl("/kx/files")) + .then(function (r) { + return r.json(); + }) + .then(function (d) { + storeFiles = d.result || []; + renderStore(); + }) + .catch(function (e) { + clog(tr("log_error", "Fehler:") + " " + e, "msg-err"); + }); +} + +function uploadGcode(file) { + if (!file) return; + var zone = document.getElementById("store-upload-zone"); + var status = document.getElementById("store-upload-status"); + var label = document.getElementById("store-upload-label"); + // Nur druckbare Dateien zulassen (Issue #59) — Drag&Drop umgeht das + // accept-Attribut, daher hier explizit prüfen. + var _fn = (file.name || "").toLowerCase(); + if (!/\.(gcode|gcode\.3mf|3mf|bgcode)$/.test(_fn)) { + if (status) { + status.textContent = + T.store_upload_only_gcode || "Only GCode files allowed"; + status.style.display = ""; + status.className = "upload-status-err"; + } + clog("Upload abgelehnt (kein GCode): " + file.name, "msg-err"); + return; + } + if (status) { + status.textContent = T.store_upload_busy; + status.style.display = ""; + status.className = "upload-status-busy"; + } + if (label) label.style.display = "none"; + if (zone) zone.style.pointerEvents = "none"; + var fd = new FormData(); + fd.append("file", file); + fd.append("web_upload", "true"); + fetch(_apiUrl("/api/files/local"), { method: "POST", body: fd }) + .then(function (r) { + if (!r.ok) + return r.text().then(function (t) { + throw new Error(r.status + ": " + t); + }); + return r.json(); + }) + .then(function () { + if (status) { + status.textContent = T.store_upload_success.replace( + "{file}", + file.name, + ); + status.className = "upload-status-ok"; + } + loadStore(); + setTimeout(function () { + if (status) { + status.style.display = "none"; + status.className = ""; + } + if (label) label.style.display = ""; + if (zone) zone.style.pointerEvents = ""; + }, 3000); + }) + .catch(function (e) { + if (status) { + status.textContent = T.store_upload_error.replace("{error}", e.message); + status.className = "upload-status-err"; + } + if (label) label.style.display = ""; + if (zone) zone.style.pointerEvents = ""; + clog(tr("log_error", "Fehler:") + " " + e, "msg-err"); + }); +} + +function renderStore() { + var grid = document.getElementById("store-grid"); + var empty = document.getElementById("store-empty"); // Suche - var q=(document.getElementById('store-search')||{value:''}).value.toLowerCase().trim(); + var q = (document.getElementById("store-search") || { value: "" }).value + .toLowerCase() + .trim(); // Filter - var filter=(document.getElementById('store-filter')||{value:'all'}).value; + var filter = (document.getElementById("store-filter") || { value: "all" }) + .value; // Sortierung - var sort=(document.getElementById('store-sort')||{value:'date_desc'}).value; + var sort = (document.getElementById("store-sort") || { value: "date_desc" }) + .value; - var files=storeFiles.filter(function(f){ - if(q&&f.filename.toLowerCase().indexOf(q)===-1) return false; - if(filter==='completed'&&f.last_print_status!=='completed') return false; - if(filter==='failed'&&(f.last_print_status!=='cancelled'&&f.last_print_status!=='failed')) return false; - if(filter==='never'&&f.last_print_status) return false; + var files = storeFiles.filter(function (f) { + if (q && f.filename.toLowerCase().indexOf(q) === -1) return false; + if (filter === "completed" && f.last_print_status !== "completed") + return false; + if ( + filter === "failed" && + f.last_print_status !== "cancelled" && + f.last_print_status !== "failed" + ) + return false; + if (filter === "never" && f.last_print_status) return false; return true; }); - files.sort(function(a,b){ - if(sort==='name_asc') return a.filename.localeCompare(b.filename); - if(sort==='duration_asc'){ - var da=a.last_print_duration||a.est_print_time_sec||0; - var db=b.last_print_duration||b.est_print_time_sec||0; - return da-db; + files.sort(function (a, b) { + if (sort === "name_asc") return a.filename.localeCompare(b.filename); + if (sort === "duration_asc") { + var da = a.last_print_duration || a.est_print_time_sec || 0; + var db = b.last_print_duration || b.est_print_time_sec || 0; + return da - db; } // date_desc (default) - return (b.uploaded_at||'').localeCompare(a.uploaded_at||''); + return (b.uploaded_at || "").localeCompare(a.uploaded_at || ""); }); - if(!storeFiles.length){ - empty.textContent=T.store_empty; - grid.innerHTML=''; - empty.style.display='block'; + if (!storeFiles.length) { + empty.textContent = T.store_empty; + grid.innerHTML = ""; + empty.style.display = "block"; return; } - if(!files.length){ - empty.textContent=T.store_no_results; - grid.innerHTML=''; - empty.style.display='block'; + if (!files.length) { + empty.textContent = T.store_no_results; + grid.innerHTML = ""; + empty.style.display = "block"; return; } - empty.style.display='none'; - grid.innerHTML=files.map(function(f){ - var thumb=f.thumbnail_b64 - ? '' - : '
🖨
'; - var name=f.filename.length>28?f.filename.slice(0,25)+'…':f.filename; - var date=f.uploaded_at?f.uploaded_at.replace('T',' ').slice(0,16):''; - var est=f.est_print_time_sec?formatDur(f.est_print_time_sec):'–'; - var statusBadge=''; - var lastInfo=''; - if(f.last_print_status==='completed'){ - statusBadge=''; - if(f.last_print_duration) lastInfo='
✓ '+formatDur(f.last_print_duration)+'
'; - } else if(f.last_print_status==='cancelled'||f.last_print_status==='failed'){ - statusBadge=''; - lastInfo='
✗ '+f.last_print_status+'
'; - } else if(!f.last_print_status){ - lastInfo='
'+T.store_never+'
'; - } - return '
'+ - thumb+ - '
'+name+statusBadge+'
'+ - lastInfo+ - '
⏱ '+T.store_estimate+': '+est+'
'+ - '
📅 '+date+'
'+ - '
'+ - ''+ - ''+ - ''+ - '
'+ - '
'; - }).join(''); + empty.style.display = "none"; + grid.innerHTML = files + .map(function (f) { + var thumb = f.thumbnail_b64 + ? '' + : '
🖨
'; + var name = + f.filename.length > 28 ? f.filename.slice(0, 25) + "…" : f.filename; + var date = f.uploaded_at + ? f.uploaded_at.replace("T", " ").slice(0, 16) + : ""; + var est = f.est_print_time_sec ? formatDur(f.est_print_time_sec) : "–"; + var statusBadge = ""; + var lastInfo = ""; + if (f.last_print_status === "completed") { + statusBadge = + ''; + if (f.last_print_duration) + lastInfo = + '
✓ ' + + formatDur(f.last_print_duration) + + "
"; + } else if ( + f.last_print_status === "cancelled" || + f.last_print_status === "failed" + ) { + statusBadge = + ''; + lastInfo = + '
✗ ' + + f.last_print_status + + "
"; + } else if (!f.last_print_status) { + lastInfo = + '
' + + T.store_never + + "
"; + } + return ( + '
' + + thumb + + '
' + + name + + statusBadge + + "
" + + lastInfo + + '
⏱ ' + + T.store_estimate + + ": " + + est + + "
" + + '
📅 ' + + date + + "
" + + '
' + + "" + + "' + + "' + + "
" + + "
" + ); + }) + .join(""); } -function formatDur(sec){ - var h=Math.floor(sec/3600),m=Math.floor((sec%3600)/60); - return h?h+'h '+m+'m':m+'m'; +function formatDur(sec) { + var h = Math.floor(sec / 3600), + m = Math.floor((sec % 3600) / 60); + return h ? h + "h " + m + "m" : m + "m"; } -var _storeFileId=null; -var _storeFilename=null; -var _filamentDialogMode='store'; // 'store' oder 'banner' -var _pendingWebVerifyFileId=null; -var _pendingWebVerifyFilename=''; -var _pendingWebVerifyAction=null; -var _pendingWebVerifyAutoOpen=false; +var _storeFileId = null; +var _storeFilename = null; +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). -var storeFiles=[]; +var storeFiles = []; -var _gcodeFilaments=[]; +var _gcodeFilaments = []; -function _setGcodeFilamentsFromFileObj(fileObj){ - try{ - if(fileObj&&Array.isArray(fileObj.gcode_filaments)){ - _gcodeFilaments=fileObj.gcode_filaments; - }else if(fileObj&&typeof fileObj.gcode_filaments==='string'&&fileObj.gcode_filaments){ - _gcodeFilaments=JSON.parse(fileObj.gcode_filaments); - }else{ - _gcodeFilaments=[]; +function _setGcodeFilamentsFromFileObj(fileObj) { + try { + if (fileObj && Array.isArray(fileObj.gcode_filaments)) { + _gcodeFilaments = fileObj.gcode_filaments; + } else if ( + fileObj && + typeof fileObj.gcode_filaments === "string" && + fileObj.gcode_filaments + ) { + _gcodeFilaments = JSON.parse(fileObj.gcode_filaments); + } else { + _gcodeFilaments = []; } - }catch(e){ - _gcodeFilaments=[]; + } catch (e) { + _gcodeFilaments = []; } } -function storePrint(fileId, filename){ - _storeFileId=fileId; - _storeFilename=filename; - _filamentDialogMode='store'; - var fileObj=storeFiles.find(function(f){return f.id===fileId;}); +function storePrint(fileId, filename) { + _storeFileId = fileId; + _storeFilename = filename; + _filamentDialogMode = "store"; + var fileObj = storeFiles.find(function (f) { + return f.id === fileId; + }); openStorePrintDialog(fileId, filename, fileObj); } -function openStorePrintDialog(fileId, filename, fileObj){ - _storeFileId=fileId; - _storeFilename=filename; - _filamentDialogMode='store'; - maybeGateWebUpload(fileObj, function(){ +function openStorePrintDialog(fileId, filename, fileObj) { + _storeFileId = fileId; + _storeFilename = filename; + _filamentDialogMode = "store"; + maybeGateWebUpload(fileObj, function () { // GCode-Filamente aus Store-Datei holen (für Vorschau im Dialog) _setGcodeFilamentsFromFileObj(fileObj); - fetch(_apiUrl('/kx/filament/slots')).then(function(r){return r.json()}).then(function(d){ - openFilamentDialog(d.result||[]); - }).catch(function(){openFilamentDialog([]);}); + fetch(_apiUrl("/kx/filament/slots")) + .then(function (r) { + return r.json(); + }) + .then(function (d) { + openFilamentDialog(d.result || []); + }) + .catch(function () { + openFilamentDialog([]); + }); }); } -function webUploadWarningEnabled(){ - return S.web_upload_warning===undefined ? true : !!S.web_upload_warning; +function webUploadWarningEnabled() { + return S.web_upload_warning === undefined ? true : !!S.web_upload_warning; } -function clearWebUploadWarningFlag(fileId, onDone){ - if(!fileId){ - if(onDone) onDone(); +function clearWebUploadWarningFlag(fileId, onDone) { + if (!fileId) { + if (onDone) onDone(); return; } - fetch(_apiUrl('/kx/files/'+encodeURIComponent(fileId)+'/verify'), {method:'POST'}) - .then(function(r){ - if(!r.ok) return r.text().then(function(t){throw new Error(r.status+': '+t);}); + fetch(_apiUrl("/kx/files/" + encodeURIComponent(fileId) + "/verify"), { + method: "POST", + }) + .then(function (r) { + if (!r.ok) + return r.text().then(function (t) { + throw new Error(r.status + ": " + t); + }); return r.json(); }) - .then(function(){ - var fileObj=(storeFiles||[]).find(function(f){return f.id===fileId;}); - if(fileObj){fileObj.web_unverified=false;} - if(onDone) onDone(); + .then(function () { + var fileObj = (storeFiles || []).find(function (f) { + return f.id === fileId; + }); + if (fileObj) { + fileObj.web_unverified = false; + } + if (onDone) onDone(); loadStore(); }) - .catch(function(e){ - clog(tr('log_error','Fehler:')+' '+e,'msg-err'); + .catch(function (e) { + clog(tr("log_error", "Fehler:") + " " + e, "msg-err"); }); } -function maybeGateWebUpload(fileObj, onContinue, opts){ - opts=opts||{}; - if(!fileObj || !fileObj.web_unverified){ - if(onContinue) onContinue(); +function maybeGateWebUpload(fileObj, onContinue, opts) { + opts = opts || {}; + if (!fileObj || !fileObj.web_unverified) { + if (onContinue) onContinue(); return; } - if(!webUploadWarningEnabled()){ - if(onContinue) onContinue(); + if (!webUploadWarningEnabled()) { + if (onContinue) onContinue(); return; } - var cancelledId=sessionStorage.getItem('webVerifyCancelledFileId')||''; - if(opts.autoOpen && cancelledId && cancelledId===String(fileObj.id||'')){ + 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); + openWebVerifyDialog( + fileObj.id, + fileObj.filename, + function () { + clearWebUploadWarningFlag(fileObj.id, onContinue); + }, + !!opts.autoOpen, + ); } -function openWebVerifyDialog(fileId, filename, onConfirm, autoOpen){ - _pendingWebVerifyFileId=fileId; - _pendingWebVerifyFilename=filename; - _pendingWebVerifyAction=onConfirm||null; - _pendingWebVerifyAutoOpen=!!autoOpen; - var status=document.getElementById('store-web-verify-status'); - if(status){status.textContent='';} +function openWebVerifyDialog(fileId, filename, onConfirm, autoOpen) { + _pendingWebVerifyFileId = fileId; + _pendingWebVerifyFilename = filename; + _pendingWebVerifyAction = onConfirm || null; + _pendingWebVerifyAutoOpen = !!autoOpen; + var status = document.getElementById("store-web-verify-status"); + if (status) { + status.textContent = ""; + } openStoreWebVerifyDialog(); } -function openStoreWebVerifyDialog(){ - var modal=document.getElementById('store-web-verify-dialog'); - if(modal){modal.classList.add('open');} -} - -function closeStoreWebVerifyDialog(){ - var modal=document.getElementById('store-web-verify-dialog'); - if(modal){modal.classList.remove('open');} - if(_pendingWebVerifyAutoOpen && _pendingWebVerifyFileId){ - sessionStorage.setItem('webVerifyCancelledFileId', String(_pendingWebVerifyFileId)); +function openStoreWebVerifyDialog() { + var modal = document.getElementById("store-web-verify-dialog"); + if (modal) { + modal.classList.add("open"); } - _pendingWebVerifyFileId=null; - _pendingWebVerifyFilename=''; - _pendingWebVerifyAction=null; - _pendingWebVerifyAutoOpen=false; } -function confirmStoreWebVerify(){ - if(!_pendingWebVerifyFileId||!_pendingWebVerifyFilename){ +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() { + if (!_pendingWebVerifyFileId || !_pendingWebVerifyFilename) { closeStoreWebVerifyDialog(); return; } - var fileId=_pendingWebVerifyFileId; - var action=_pendingWebVerifyAction; - var status=document.getElementById('store-web-verify-status'); - if(status){status.textContent='…';} - fetch(_apiUrl('/kx/files/'+encodeURIComponent(fileId)+'/verify'), {method:'POST'}) - .then(function(r){ - if(!r.ok) return r.text().then(function(t){throw new Error(r.status+': '+t);}); + var fileId = _pendingWebVerifyFileId; + var action = _pendingWebVerifyAction; + var status = document.getElementById("store-web-verify-status"); + if (status) { + status.textContent = "…"; + } + fetch(_apiUrl("/kx/files/" + encodeURIComponent(fileId) + "/verify"), { + method: "POST", + }) + .then(function (r) { + if (!r.ok) + return r.text().then(function (t) { + throw new Error(r.status + ": " + t); + }); return r.json(); }) - .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; + .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(); + if (typeof action === "function") action(); }) - .catch(function(e){ - if(status){status.textContent='✗ '+e.message;} - clog(tr('log_error','Fehler:')+' '+e,'msg-err'); + .catch(function (e) { + if (status) { + status.textContent = "✗ " + e.message; + } + clog(tr("log_error", "Fehler:") + " " + e, "msg-err"); }); } -function startReadyFileWithSlots(filename,_autoOpen){ - if(!_autoOpen) _fdAutoOpenedFile=null; // manueller Aufruf → Auto-Open-Sperre aufheben - 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}); +function startReadyFileWithSlots(filename, _autoOpen) { + if (!_autoOpen) _fdAutoOpenedFile = null; // manueller Aufruf → Auto-Open-Sperre aufheben + 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 }, + ); return; } - _filamentDialogMode='banner'; - _storeFilename=fn||''; + _filamentDialogMode = "banner"; + _storeFilename = fn || ""; // Banner must never reuse stale store-file context. - _storeFileId=null; - _gcodeFilaments=[]; + _storeFileId = null; + _gcodeFilaments = []; - var _autoOpenFile=_autoOpen?fn:null; - if(_autoOpen) _fdDialogOpen=true; // bereits während Fetch sperren - function openWithSlots(){ - fetch(_apiUrl('/kx/filament/slots')).then(function(r){return r.json()}).then(function(d){ - if(_autoOpenFile && _fdUserCancelled){_fdDialogOpen=false;return;} - openFilamentDialog(d.result||[]); - }).catch(function(){ - if(_autoOpenFile && _fdUserCancelled){_fdDialogOpen=false;return;} - openFilamentDialog([]); - }); + var _autoOpenFile = _autoOpen ? fn : null; + if (_autoOpen) _fdDialogOpen = true; // bereits während Fetch sperren + function openWithSlots() { + fetch(_apiUrl("/kx/filament/slots")) + .then(function (r) { + return r.json(); + }) + .then(function (d) { + if (_autoOpenFile && _fdUserCancelled) { + _fdDialogOpen = false; + return; + } + openFilamentDialog(d.result || []); + }) + .catch(function () { + if (_autoOpenFile && _fdUserCancelled) { + _fdDialogOpen = false; + return; + } + openFilamentDialog([]); + }); } - function _proceedWithFileObj(fileObj){ - if(fileObj && fileObj.web_unverified && webUploadWarningEnabled()){ + 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}); + if (_autoOpen) { + _fdDialogOpen = false; + } + maybeGateWebUpload( + fileObj, + function () { + startReadyFileWithSlots(fn, _autoOpen); + }, + { autoOpen: !!_autoOpen }, + ); return; } - if(fileObj){ - _storeFileId=fileObj.id; + if (fileObj) { + _storeFileId = fileObj.id; _setGcodeFilamentsFromFileObj(fileObj); } openWithSlots(); } - var fileObj=(storeFiles||[]).find(function(f){return f.filename===_storeFilename;}); - if(fileObj){ + var fileObj = (storeFiles || []).find(function (f) { + return f.filename === _storeFilename; + }); + if (fileObj) { _proceedWithFileObj(fileObj); 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); - }).catch(function(){ - openWithSlots(); - }); + 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); + }) + .catch(function () { + openWithSlots(); + }); } -var _amsSlots=[]; -var _printObjects=[]; // [{name, skip}] für aktuell offenen Dialog -var _printObjectsSvg=''; // base64-SVG aus DB für Visualisierung +var _amsSlots = []; +var _printObjects = []; // [{name, skip}] für aktuell offenen Dialog +var _printObjectsSvg = ""; // base64-SVG aus DB für Visualisierung // Hilfsfunktionen für farbige Kanal/Slot-Marker (Issue #23) -function _contrastText(hex){ +function _contrastText(hex) { // Helle Farbe → dunkler Text, dunkle Farbe → heller Text - var c=(hex||'').replace('#',''); - if(c.length===3)c=c[0]+c[0]+c[1]+c[1]+c[2]+c[2]; - if(c.length<6)return '#fff'; - var r=parseInt(c.slice(0,2),16),g=parseInt(c.slice(2,4),16),b=parseInt(c.slice(4,6),16); + var c = (hex || "").replace("#", ""); + if (c.length === 3) c = c[0] + c[0] + c[1] + c[1] + c[2] + c[2]; + if (c.length < 6) return "#fff"; + var r = parseInt(c.slice(0, 2), 16), + g = parseInt(c.slice(2, 4), 16), + b = parseInt(c.slice(4, 6), 16); // YIQ-Helligkeit - var y=(r*299 + g*587 + b*114)/1000; - return y>=140?'#111':'#fff'; + var y = (r * 299 + g * 587 + b * 114) / 1000; + return y >= 140 ? "#111" : "#fff"; } -function _normalizeMaterialKey(material){ - var key=(material||'').toUpperCase().replace(/[^A-Z0-9+]/g,''); +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'; + 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=0) return w; + 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){ - return _normalizeMaterialKey(a)===_normalizeMaterialKey(b); +function _materialsCompatible(a, b) { + return _normalizeMaterialKey(a) === _normalizeMaterialKey(b); } // Issue #57 Punkt 4: konkreter Profilname (User-Override) statt generischem Typ. // Fällt auf den Material-Typ zurück wenn kein Profil gemappt ist. -function _slotProfileLabel(slot){ - if(!slot)return ''; - if(slot.filament_name){ - return slot.filament_name+(slot.filament_vendor?' — '+slot.filament_vendor:''); +function _slotProfileLabel(slot) { + if (!slot) return ""; + if (slot.filament_name) { + return ( + slot.filament_name + + (slot.filament_vendor ? " — " + slot.filament_vendor : "") + ); } - return slot.material||''; + return slot.material || ""; } -function _escAttr(s){ - return String(s||'').replace(/&/g,'&').replace(/"/g,'"').replace(//g,'>'); +function _escAttr(s) { + return String(s || "") + .replace(/&/g, "&") + .replace(/"/g, """) + .replace(//g, ">"); } -function _updateSlotMarker(sel){ - var opt=sel.options[sel.selectedIndex]; - var color=opt&&opt.dataset.color?opt.dataset.color:'#888'; - var slotIdx=parseInt(opt.value); - var paintIdx=sel.dataset.paint; - var marker=document.querySelector('.fd-slot-marker[data-for-paint="'+paintIdx+'"]'); - if(marker){ - marker.style.background=color; - marker.style.color=_contrastText(color); - marker.textContent=(slotIdx+1); - marker.title=(opt&&opt.dataset.profile)?opt.dataset.profile:''; +function _updateSlotMarker(sel) { + var opt = sel.options[sel.selectedIndex]; + var color = opt && opt.dataset.color ? opt.dataset.color : "#888"; + var slotIdx = parseInt(opt.value); + var paintIdx = sel.dataset.paint; + var marker = document.querySelector( + '.fd-slot-marker[data-for-paint="' + paintIdx + '"]', + ); + if (marker) { + marker.style.background = color; + marker.style.color = _contrastText(color); + marker.textContent = slotIdx + 1; + marker.title = opt && opt.dataset.profile ? opt.dataset.profile : ""; } } -function openFilamentDialog(slots){ - _amsSlots=slots - .filter(function(s){return s.status==='loaded';}) - .sort(function(a,b){return (a.slot_index||0)-(b.slot_index||0);}); - var dlg=document.getElementById('filament-dialog'); - var title=document.getElementById('fd-title'); - var body=document.getElementById('fd-slots'); - if(title)title.textContent='▶ '+_storeFilename; +function openFilamentDialog(slots) { + _amsSlots = slots + .filter(function (s) { + return s.status === "loaded"; + }) + .sort(function (a, b) { + return (a.slot_index || 0) - (b.slot_index || 0); + }); + var dlg = document.getElementById("filament-dialog"); + var title = document.getElementById("fd-title"); + var body = document.getElementById("fd-slots"); + if (title) title.textContent = "▶ " + _storeFilename; // Auto-Leveling-Checkbox mit globalem Default vorbelegen - var fdAl=document.getElementById('fd-auto-leveling'); - if(fdAl) fdAl.checked=(S.auto_leveling===undefined?true:!!S.auto_leveling); + var fdAl = document.getElementById("fd-auto-leveling"); + if (fdAl) + fdAl.checked = S.auto_leveling === undefined ? true : !!S.auto_leveling; // Objekt-Liste laden — sobald eine File-ID auflösbar ist (Issue #57 Punkt 3: // Skip-Parität auch im Banner-/Upload-Modus, nicht nur im Store-Modus). // startReadyFileWithSlots() setzt _storeFileId auch im Banner-Modus per // filename→fileObj-Lookup, daher reicht hier die _storeFileId-Prüfung. - _printObjects=[]; - _printObjectsSvg=''; - var objSection=document.getElementById('fd-objects-section'); - var objBody=document.getElementById('fd-objects-body'); - var objArrow=document.getElementById('fd-objects-arrow'); - if(objSection)objSection.style.display='none'; - if(objBody)objBody.style.display='none'; // immer eingeklappt starten - if(objArrow)objArrow.style.transform=''; - if(_storeFileId){ + _printObjects = []; + _printObjectsSvg = ""; + var objSection = document.getElementById("fd-objects-section"); + var objBody = document.getElementById("fd-objects-body"); + var objArrow = document.getElementById("fd-objects-arrow"); + if (objSection) objSection.style.display = "none"; + 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){ + 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(){}); + }) + .catch(function () {}); })(); } // GCode-Kanäle: bevorzugt aus _gcodeFilaments, sonst aus belegten AMS-Slots ableiten - var channels=_gcodeFilaments.length?_gcodeFilaments:_amsSlots.map(function(s,i){ - return {slot_index:i,color_hex:s.color_hex,material:s.material}; - }); + var channels = _gcodeFilaments.length + ? _gcodeFilaments + : _amsSlots.map(function (s, i) { + return { slot_index: i, color_hex: s.color_hex, material: s.material }; + }); // Default mapping strategy: // 1) keep order where possible (row i -> nearest compatible slot i) // 2) keep defaults unique while compatible slots are available // 3) use color proximity as tie-breaker - function _hexToRgb(hex){ - var c=(hex||'').replace('#',''); - if(c.length===3)c=c[0]+c[0]+c[1]+c[1]+c[2]+c[2]; - if(c.length<6)return [255,255,255]; - return [parseInt(c.slice(0,2),16),parseInt(c.slice(2,4),16),parseInt(c.slice(4,6),16)]; + function _hexToRgb(hex) { + var c = (hex || "").replace("#", ""); + if (c.length === 3) c = c[0] + c[0] + c[1] + c[1] + c[2] + c[2]; + if (c.length < 6) return [255, 255, 255]; + return [ + parseInt(c.slice(0, 2), 16), + parseInt(c.slice(2, 4), 16), + parseInt(c.slice(4, 6), 16), + ]; } - function _colorDist(a,b){ - var ar=_hexToRgb(a), br=_hexToRgb(b); - var dr=ar[0]-br[0], dg=ar[1]-br[1], db=ar[2]-br[2]; - return (dr*dr + dg*dg + db*db); + function _colorDist(a, b) { + var ar = _hexToRgb(a), + br = _hexToRgb(b); + var dr = ar[0] - br[0], + dg = ar[1] - br[1], + db = ar[2] - br[2]; + return dr * dr + dg * dg + db * db; } - var defaultSlotByPaint={}; - var usedDefaultSlot={}; - channels.forEach(function(gc,i){ - var compatible=_amsSlots.filter(function(s){ + var defaultSlotByPaint = {}; + var usedDefaultSlot = {}; + channels.forEach(function (gc, i) { + var compatible = _amsSlots.filter(function (s) { return _materialsCompatible(gc.material, s.material); }); - if(!compatible.length){ - defaultSlotByPaint[i]=-1; + if (!compatible.length) { + defaultSlotByPaint[i] = -1; return; } - var ranked=compatible.slice().sort(function(a,b){ - var da=Math.abs((a.slot_index||0)-i), db=Math.abs((b.slot_index||0)-i); - if(da!==db)return da-db; - var ca=_colorDist(gc.color_hex, a.color_hex), cb=_colorDist(gc.color_hex, b.color_hex); - if(ca!==cb)return ca-cb; - return (a.slot_index||0)-(b.slot_index||0); + var ranked = compatible.slice().sort(function (a, b) { + var da = Math.abs((a.slot_index || 0) - i), + db = Math.abs((b.slot_index || 0) - i); + if (da !== db) return da - db; + var ca = _colorDist(gc.color_hex, a.color_hex), + cb = _colorDist(gc.color_hex, b.color_hex); + if (ca !== cb) return ca - cb; + return (a.slot_index || 0) - (b.slot_index || 0); }); - var chosen=ranked.find(function(s){return !usedDefaultSlot[s.slot_index];}) || ranked[0]; - defaultSlotByPaint[i]=chosen?chosen.slot_index:-1; - if(chosen) usedDefaultSlot[chosen.slot_index]=1; + var chosen = + ranked.find(function (s) { + return !usedDefaultSlot[s.slot_index]; + }) || ranked[0]; + defaultSlotByPaint[i] = chosen ? chosen.slot_index : -1; + if (chosen) usedDefaultSlot[chosen.slot_index] = 1; }); - if(!_amsSlots.length){ - body.innerHTML='

'+T.fd_no_slots_msg.replace('{br}','
')+'

'; + if (!_amsSlots.length) { + body.innerHTML = + '

' + + T.fd_no_slots_msg.replace("{br}", "
") + + "

"; } else { - body.innerHTML=channels.map(function(gc,i){ - var isUsed=(gc&&gc.is_used!==false); - // Only allow material-compatible slots. - var compatible=_amsSlots.filter(function(s){ - return _materialsCompatible(gc.material, s.material); - }); + body.innerHTML = channels + .map(function (gc, i) { + var isUsed = gc && gc.is_used !== false; + // Only allow material-compatible slots. + var compatible = _amsSlots.filter(function (s) { + return _materialsCompatible(gc.material, s.material); + }); - var defaultSlotIndex=(defaultSlotByPaint.hasOwnProperty(i)?defaultSlotByPaint[i]:-1); - var defaultSlot=compatible.find(function(s){return s.slot_index===defaultSlotIndex;})||null; - var opts=compatible.map(function(s){ - var sel=(defaultSlot&&s.slot_index===defaultSlot.slot_index)?'selected':''; - return ''; - }).join(''); - if(!compatible.length){ - opts=''; - } - // Kanal-Box (links): farbige Box mit Nummer + auto Kontrast-Text - var txt=_contrastText(gc.color_hex); - var slotColor=defaultSlot?defaultSlot.color_hex:'#888'; - var slotTxt=_contrastText(slotColor); - var usedBadge=isUsed - ? ''+T.fd_used+'' - : ''+T.fd_used+''; - return '
'+ - ''+(i+1)+''+ - ''+gc.material+''+ - usedBadge+ - ''+ - ''+(defaultSlot?defaultSlot.slot_index+1:'?')+''+ - ''+ - '
'; - }).join(''); + var defaultSlotIndex = defaultSlotByPaint.hasOwnProperty(i) + ? defaultSlotByPaint[i] + : -1; + var defaultSlot = + compatible.find(function (s) { + return s.slot_index === defaultSlotIndex; + }) || null; + var opts = compatible + .map(function (s) { + var sel = + defaultSlot && s.slot_index === defaultSlot.slot_index + ? "selected" + : ""; + return ( + '" + ); + }) + .join(""); + if (!compatible.length) { + opts = + '"; + } + // Kanal-Box (links): farbige Box mit Nummer + auto Kontrast-Text + var txt = _contrastText(gc.color_hex); + var slotColor = defaultSlot ? defaultSlot.color_hex : "#888"; + var slotTxt = _contrastText(slotColor); + var usedBadge = isUsed + ? '' + + T.fd_used + + "" + : '' + + T.fd_used + + ""; + return ( + '
' + + '' + + (i + 1) + + "" + + '' + + gc.material + + "" + + usedBadge + + '' + + '' + + (defaultSlot ? defaultSlot.slot_index + 1 : "?") + + "" + + '" + + "
" + ); + }) + .join(""); } - if(dlg)dlg.classList.add('open'); + if (dlg) dlg.classList.add("open"); } -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); +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); } } -function confirmFilamentPrint(){ - var selects=document.querySelectorAll('#fd-slots select'); - var assignments=[]; - var missingCompatible=0; - selects.forEach(function(sel){ - var paintIdx=parseInt(sel.dataset.paint); - var paintColor=sel.dataset.paintColor; - var isUsed=(sel.dataset.isUsed==='1'); - var hasCompatible=(sel.dataset.hasCompatible==='1'); - var opt=sel.options[sel.selectedIndex]; - var amsIdx=parseInt(opt&&opt.value); - if(!hasCompatible || Number.isNaN(amsIdx) || amsIdx < 0){ - if(isUsed) missingCompatible += 1; +function confirmFilamentPrint() { + var selects = document.querySelectorAll("#fd-slots select"); + var assignments = []; + var missingCompatible = 0; + selects.forEach(function (sel) { + var paintIdx = parseInt(sel.dataset.paint); + var paintColor = sel.dataset.paintColor; + var isUsed = sel.dataset.isUsed === "1"; + var hasCompatible = sel.dataset.hasCompatible === "1"; + var opt = sel.options[sel.selectedIndex]; + var amsIdx = parseInt(opt && opt.value); + if (!hasCompatible || Number.isNaN(amsIdx) || amsIdx < 0) { + if (isUsed) missingCompatible += 1; amsIdx = -1; } - var amsSlot=_amsSlots.find(function(s){return s.slot_index===amsIdx;})||{}; + var amsSlot = + _amsSlots.find(function (s) { + return s.slot_index === amsIdx; + }) || {}; // Farbe als [R,G,B,255] - function hexToRgba(h){ - var c=h.replace('#',''); - if(c.length===3)c=c[0]+c[0]+c[1]+c[1]+c[2]+c[2]; - return [parseInt(c.slice(0,2),16),parseInt(c.slice(2,4),16),parseInt(c.slice(4,6),16),255]; + function hexToRgba(h) { + var c = h.replace("#", ""); + if (c.length === 3) c = c[0] + c[0] + c[1] + c[1] + c[2] + c[2]; + return [ + parseInt(c.slice(0, 2), 16), + parseInt(c.slice(2, 4), 16), + parseInt(c.slice(4, 6), 16), + 255, + ]; } assignments.push({ - paint_index: paintIdx, - is_used: isUsed, - slot_index: amsIdx, - material: opt.dataset.material||'PLA', - paint_color: hexToRgba(paintColor||'#ffffff'), - ams_color: hexToRgba(amsSlot.color_hex||'#ffffff'), + paint_index: paintIdx, + is_used: isUsed, + slot_index: amsIdx, + material: opt.dataset.material || "PLA", + paint_color: hexToRgba(paintColor || "#ffffff"), + ams_color: hexToRgba(amsSlot.color_hex || "#ffffff"), }); }); - if(missingCompatible>0){ - clog('Cannot start print: '+missingCompatible+' used paint(s) have no matching material slot','msg-err'); + if (missingCompatible > 0) { + clog( + "Cannot start print: " + + missingCompatible + + " used paint(s) have no matching material slot", + "msg-err", + ); return; } // Pre-Print Skip: Namen der abgehakten Objekte sammeln - var excludedObjects=_printObjects.filter(function(o){return o.skip;}).map(function(o){return o.name;}); - var fdAlEl=document.getElementById('fd-auto-leveling'); - var fdAutoLeveling=fdAlEl?( fdAlEl.checked?1:0):(S.auto_leveling===undefined?1:S.auto_leveling?1:0); + var excludedObjects = _printObjects + .filter(function (o) { + return o.skip; + }) + .map(function (o) { + return o.name; + }); + var fdAlEl = document.getElementById("fd-auto-leveling"); + var fdAutoLeveling = fdAlEl + ? fdAlEl.checked + ? 1 + : 0 + : S.auto_leveling === undefined + ? 1 + : S.auto_leveling + ? 1 + : 0; closeFilamentDialog(); - if(_filamentDialogMode==='banner'){ + if (_filamentDialogMode === "banner") { // Banner-Modus: /kx/print bevorzugen wenn _storeFileId bekannt (gleicher Pfad wie File-Browser). - var btn=document.getElementById('file-ready-btn'); - if(btn){btn.disabled=true;btn.textContent='…';} + 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 - }) + 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, + }), }); - }else{ - startPromise=post('/printer/print/start',{ - filename:S.file_ready||_storeFilename, - filament_assignments:assignments, - excluded_objects:excludedObjects, - auto_leveling:fdAutoLeveling + } 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','Fehler:')+' '+e,'msg-err'); - if(btn){btn.disabled=false;setText('file-ready-btn',T.file_ready_btn);} - }); + 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", "Fehler:") + " " + 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'),{ - method:'POST', - headers:{'Content-Type':'application/json'}, - body:JSON.stringify({file_id:_storeFileId,filament_assignments:assignments,excluded_objects:excludedObjects,auto_leveling:fdAutoLeveling}) - }).then(function(r){return r.json()}).then(function(d){ - if(d.result==='ok'){clog(tr('log_print_start','Druckstart:')+' '+_storeFilename,'msg-ok');showPanel('dashboard');} - else{clog(tr('log_error','Fehler:')+' '+(d.error||'?'),'msg-err');} - }).catch(function(e){clog(tr('log_error','Fehler:')+' '+e,'msg-err');}); + 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, + }), + }) + .then(function (r) { + return r.json(); + }) + .then(function (d) { + if (d.result === "ok") { + clog( + tr("log_print_start", "Druckstart:") + " " + _storeFilename, + "msg-ok", + ); + showPanel("dashboard"); + } else { + clog(tr("log_error", "Fehler:") + " " + (d.error || "?"), "msg-err"); + } + }) + .catch(function (e) { + clog(tr("log_error", "Fehler:") + " " + e, "msg-err"); + }); } } -function renderObjectChecklist(){ - var box=document.getElementById('fd-objects'); - if(!box)return; - box.innerHTML=_printObjects.map(function(o,i){ - var label=o.name; - // Klipper-Namen sind oft "Datei.stl_id_N_copy_M" → schöner darstellen - var m=label.match(/^(.+)\.stl_id_(\d+)_copy_(\d+)$/); - if(m)label=m[1]+' #'+(parseInt(m[2])+1)+(m[3]!=='0'?' ('+(parseInt(m[3])+1)+')':''); - return ''; - }).join(''); +function renderObjectChecklist() { + var box = document.getElementById("fd-objects"); + if (!box) return; + box.innerHTML = _printObjects + .map(function (o, i) { + var label = o.name; + // Klipper-Namen sind oft "Datei.stl_id_N_copy_M" → schöner darstellen + var m = label.match(/^(.+)\.stl_id_(\d+)_copy_(\d+)$/); + if (m) + label = + m[1] + + " #" + + (parseInt(m[2]) + 1) + + (m[3] !== "0" ? " (" + (parseInt(m[3]) + 1) + ")" : ""); + return ( + '" + ); + }) + .join(""); } -function _toggleObjectSkip(idx,val){ - if(_printObjects[idx])_printObjects[idx].skip=!!val; +function _toggleObjectSkip(idx, val) { + if (_printObjects[idx]) _printObjects[idx].skip = !!val; renderObjectSvg(); } // Issue #57 Punkt 3: Skip-Objekte-Bereich ein-/ausklappen -function toggleFdObjects(){ - var body=document.getElementById('fd-objects-body'); - var arrow=document.getElementById('fd-objects-arrow'); - if(!body)return; - var open=body.style.display!=='none'; - body.style.display=open?'none':'block'; - if(arrow)arrow.style.transform=open?'':'rotate(90deg)'; +function toggleFdObjects() { + var body = document.getElementById("fd-objects-body"); + var arrow = document.getElementById("fd-objects-arrow"); + if (!body) return; + var open = body.style.display !== "none"; + body.style.display = open ? "none" : "block"; + if (arrow) arrow.style.transform = open ? "" : "rotate(90deg)"; } -function renderObjectSvg(){ - var box=document.getElementById('fd-objects-svg'); - if(!box)return; - if(!_printObjectsSvg||!_printObjects.length){box.style.display='none';box.innerHTML='';return;} - box.style.display='block'; - var svg=''; try{ svg=atob(_printObjectsSvg);}catch(e){ box.style.display='none'; return; } - box.innerHTML=svg; - var svgEl=box.querySelector('svg'); - if(!svgEl)return; - svgEl.style.width='100%'; svgEl.style.maxHeight='200px'; svgEl.style.height='auto'; - _printObjects.forEach(function(o,i){ - var g=svgEl.querySelector('g[id="'+CSS.escape(o.name)+'"]'); - if(!g)return; - var path=g.querySelector('path'); - g.style.cursor='pointer'; - g.setAttribute('opacity', o.skip?'0.8':'0.35'); - if(path){ - path.setAttribute('fill', o.skip?'#ff5e5b':'#5fa7ff'); - path.setAttribute('fill-opacity', o.skip?'0.4':'0.18'); +function renderObjectSvg() { + var box = document.getElementById("fd-objects-svg"); + if (!box) return; + if (!_printObjectsSvg || !_printObjects.length) { + box.style.display = "none"; + box.innerHTML = ""; + return; + } + box.style.display = "block"; + var svg = ""; + try { + svg = atob(_printObjectsSvg); + } catch (e) { + box.style.display = "none"; + return; + } + box.innerHTML = svg; + var svgEl = box.querySelector("svg"); + if (!svgEl) return; + svgEl.style.width = "100%"; + svgEl.style.maxHeight = "200px"; + svgEl.style.height = "auto"; + _printObjects.forEach(function (o, i) { + var g = svgEl.querySelector('g[id="' + CSS.escape(o.name) + '"]'); + if (!g) return; + var path = g.querySelector("path"); + g.style.cursor = "pointer"; + g.setAttribute("opacity", o.skip ? "0.8" : "0.35"); + if (path) { + path.setAttribute("fill", o.skip ? "#ff5e5b" : "#5fa7ff"); + path.setAttribute("fill-opacity", o.skip ? "0.4" : "0.18"); } - g.onclick=function(){ - _printObjects[i].skip=!_printObjects[i].skip; - renderObjectChecklist(); renderObjectSvg(); + g.onclick = function () { + _printObjects[i].skip = !_printObjects[i].skip; + renderObjectChecklist(); + 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||[]; +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]}; + var prevWillSkip = {}; + (_skipObjects || []).forEach(function (o) { + if (o && o.name) prevWillSkip[o.name] = !!o.willSkip; }); - renderSkipList(); renderSkipSvg(); + _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; +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; + 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'); + }, 2000); + document.getElementById("skip-dialog").classList.add("open"); } -function _refreshSkipDialog(){ +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(){}); + 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 () {}); }); } -function closeSkipDialog(){ - if(_skipPollTimer){clearInterval(_skipPollTimer);_skipPollTimer=null;} - document.getElementById('skip-dialog').classList.remove('open'); +function closeSkipDialog() { + if (_skipPollTimer) { + clearInterval(_skipPollTimer); + _skipPollTimer = null; + } + document.getElementById("skip-dialog").classList.remove("open"); } -function _shortLabel(name){ - var m=name.match(/^(.+)\.[sS][tT][lL]_id_(\d+)_copy_(\d+)$/); - if(!m)return name; - return m[1]+' #'+(parseInt(m[2])+1)+(m[3]!=='0'?' ('+(parseInt(m[3])+1)+')':''); +function _shortLabel(name) { + var m = name.match(/^(.+)\.[sS][tT][lL]_id_(\d+)_copy_(\d+)$/); + if (!m) return name; + return ( + m[1] + + " #" + + (parseInt(m[2]) + 1) + + (m[3] !== "0" ? " (" + (parseInt(m[3]) + 1) + ")" : "") + ); } -function renderSkipList(){ - var box=document.getElementById('skip-list'); - if(!box)return; - if(!_skipObjects.length){ - box.innerHTML='
'+tr('skip_no_objects')+'
'; +function renderSkipList() { + var box = document.getElementById("skip-list"); + if (!box) return; + if (!_skipObjects.length) { + box.innerHTML = + '
' + + tr("skip_no_objects") + + "
"; return; } - box.innerHTML=_skipObjects.map(function(o,i){ - var label=_shortLabel(o.name); - var dis=o.skipped?'disabled':''; - var note=o.skipped?''+tr('skip_already')+'':''; - return ''; - }).join(''); + box.innerHTML = _skipObjects + .map(function (o, i) { + var label = _shortLabel(o.name); + var dis = o.skipped ? "disabled" : ""; + var note = o.skipped + ? '' + + tr("skip_already") + + "" + : ""; + return ( + '" + ); + }) + .join(""); } -function renderSkipSvg(){ - var box=document.getElementById('skip-svg'); - if(!box)return; - if(!_skipSvg||!_skipObjects.length){box.style.display='none';box.innerHTML='';return;} - box.style.display='block'; +function renderSkipSvg() { + var box = document.getElementById("skip-svg"); + if (!box) return; + if (!_skipSvg || !_skipObjects.length) { + box.style.display = "none"; + box.innerHTML = ""; + return; + } + box.style.display = "block"; // SVG aus base64 dekodieren - var svg=''; - try{ svg=atob(_skipSvg); }catch(e){ box.style.display='none'; return; } - box.innerHTML=svg; + var svg = ""; + try { + svg = atob(_skipSvg); + } catch (e) { + box.style.display = "none"; + return; + } + box.innerHTML = svg; // Polygone interaktiv machen: jeder entspricht einem Objekt - var svgEl=box.querySelector('svg'); - if(!svgEl)return; - svgEl.style.width='100%'; svgEl.style.maxHeight='280px'; svgEl.style.height='auto'; - _skipObjects.forEach(function(o,i){ - var g=svgEl.querySelector('g[id="'+CSS.escape(o.name)+'"]'); - if(!g)return; - var path=g.querySelector('path'); - if(o.skipped){ + var svgEl = box.querySelector("svg"); + if (!svgEl) return; + svgEl.style.width = "100%"; + svgEl.style.maxHeight = "280px"; + svgEl.style.height = "auto"; + _skipObjects.forEach(function (o, i) { + var g = svgEl.querySelector('g[id="' + CSS.escape(o.name) + '"]'); + if (!g) return; + var path = g.querySelector("path"); + if (o.skipped) { // bereits übersprungen → ausgegraut, kein Klick - g.setAttribute('opacity','0.25'); - if(path){path.setAttribute('fill','#888');path.setAttribute('fill-opacity','0.3');} - g.style.cursor='not-allowed'; - } else { - g.style.cursor='pointer'; - g.setAttribute('opacity', o.willSkip?'0.8':'0.35'); - if(path){ - path.setAttribute('fill', o.willSkip?'#ff5e5b':'#5fa7ff'); - path.setAttribute('fill-opacity', o.willSkip?'0.4':'0.18'); + g.setAttribute("opacity", "0.25"); + if (path) { + path.setAttribute("fill", "#888"); + path.setAttribute("fill-opacity", "0.3"); } - g.onclick=function(){ - _skipObjects[i].willSkip=!_skipObjects[i].willSkip; - renderSkipList(); renderSkipSvg(); + g.style.cursor = "not-allowed"; + } else { + g.style.cursor = "pointer"; + g.setAttribute("opacity", o.willSkip ? "0.8" : "0.35"); + if (path) { + path.setAttribute("fill", o.willSkip ? "#ff5e5b" : "#5fa7ff"); + path.setAttribute("fill-opacity", o.willSkip ? "0.4" : "0.18"); + } + g.onclick = function () { + _skipObjects[i].willSkip = !_skipObjects[i].willSkip; + renderSkipList(); + renderSkipSvg(); }; } }); } -function _toggleWillSkip(idx,val){ - if(_skipObjects[idx])_skipObjects[idx].willSkip=!!val; +function _toggleWillSkip(idx, val) { + if (_skipObjects[idx]) _skipObjects[idx].willSkip = !!val; renderSkipSvg(); } -function confirmSkip(){ - var names=_skipObjects.filter(function(o){return o.willSkip;}).map(function(o){return o.name;}); - var st=document.getElementById('skip-status'); - var btn=document.getElementById('skip-confirm'); - if(!names.length){st.textContent=tr('skip_select_at_least_one');st.style.color='var(--warn)';return;} - btn.disabled=true; st.textContent=tr('skip_sending'); st.style.color='var(--txt2)'; - fetch(_apiUrl('/kx/skip'),{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({names:names})}) - .then(function(r){return r.json().then(function(j){return {ok:r.ok,j:j};});}) - .then(function(res){ - if(!res.ok){st.textContent=(res.j&&res.j.error)||tr('log_error','Fehler:');st.style.color='var(--err)';btn.disabled=false;return;} - st.textContent=tr('skip_success');st.style.color='var(--ok)'; - // Dialog offen lassen + neu laden damit der "übersprungen"-Status erscheint - setTimeout(function(){ _refreshSkipDialog(); btn.disabled=false; st.textContent=''; }, 1500); +function confirmSkip() { + var names = _skipObjects + .filter(function (o) { + return o.willSkip; }) - .catch(function(e){st.textContent=''+e;st.style.color='var(--err)';btn.disabled=false;}); + .map(function (o) { + return o.name; + }); + var st = document.getElementById("skip-status"); + var btn = document.getElementById("skip-confirm"); + if (!names.length) { + st.textContent = tr("skip_select_at_least_one"); + st.style.color = "var(--warn)"; + return; + } + btn.disabled = true; + st.textContent = tr("skip_sending"); + st.style.color = "var(--txt2)"; + fetch(_apiUrl("/kx/skip"), { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ names: names }), + }) + .then(function (r) { + return r.json().then(function (j) { + return { ok: r.ok, j: j }; + }); + }) + .then(function (res) { + if (!res.ok) { + st.textContent = (res.j && res.j.error) || tr("log_error", "Fehler:"); + st.style.color = "var(--err)"; + btn.disabled = false; + return; + } + st.textContent = tr("skip_success"); + st.style.color = "var(--ok)"; + // Dialog offen lassen + neu laden damit der "übersprungen"-Status erscheint + setTimeout(function () { + _refreshSkipDialog(); + btn.disabled = false; + st.textContent = ""; + }, 1500); + }) + .catch(function (e) { + st.textContent = "" + e; + st.style.color = "var(--err)"; + btn.disabled = false; + }); } -function storeDelete(fileId){ - if(!confirm(T.store_delete_confirm)) return; - fetch(_apiUrl('/kx/files/'+fileId),{method:'DELETE'}).then(function(r){ - if(r.ok){loadStore();} - else{clog(tr('log_delete_failed','Löschen fehlgeschlagen'),'msg-err');} - }); +function storeDelete(fileId) { + if (!confirm(T.store_delete_confirm)) return; + fetch(_apiUrl("/kx/files/" + fileId), { method: "DELETE" }).then( + function (r) { + if (r.ok) { + loadStore(); + } else { + clog(tr("log_delete_failed", "Löschen fehlgeschlagen"), "msg-err"); + } + }, + ); } -function storeDownload(fileId){ - var a=document.createElement('a'); - a.href=_apiUrl('/kx/files/'+encodeURIComponent(fileId)+'/download'); - a.style.display='none'; +function storeDownload(fileId) { + var a = document.createElement("a"); + a.href = _apiUrl("/kx/files/" + encodeURIComponent(fileId) + "/download"); + a.style.display = "none"; document.body.appendChild(a); a.click(); a.remove(); } // ── Drucker hinzufügen ── -function openAddPrinterDialog(){ - document.getElementById('apd-ip').value=''; - document.getElementById('apd-name').value=''; - var st=document.getElementById('apd-status');st.textContent='';st.style.color='var(--txt2)'; - document.getElementById('apd-confirm').disabled=false; - document.getElementById('add-printer-dialog').classList.add('open'); +function openAddPrinterDialog() { + document.getElementById("apd-ip").value = ""; + document.getElementById("apd-name").value = ""; + var st = document.getElementById("apd-status"); + st.textContent = ""; + st.style.color = "var(--txt2)"; + document.getElementById("apd-confirm").disabled = false; + document.getElementById("add-printer-dialog").classList.add("open"); } -function closeAddPrinterDialog(){ - document.getElementById('add-printer-dialog').classList.remove('open'); +function closeAddPrinterDialog() { + document.getElementById("add-printer-dialog").classList.remove("open"); } -function confirmAddPrinter(){ - var ip=document.getElementById('apd-ip').value.trim(); - var name=document.getElementById('apd-name').value.trim(); - var st=document.getElementById('apd-status'),btn=document.getElementById('apd-confirm'); - if(!ip){st.textContent=T.apd_err_ip;st.style.color='var(--err)';return;} - st.textContent=T.apd_fetching;st.style.color='var(--txt2)';btn.disabled=true; - fetch('/kx/printers/add',{method:'POST',headers:{'Content-Type':'application/json'}, - body:JSON.stringify({printer_ip:ip,name:name})}) - .then(function(r){return r.json().then(function(j){return {ok:r.ok,j:j};});}) - .then(function(res){ - if(!res.ok){st.textContent=(res.j&&res.j.error)||tr('log_error','Fehler:');st.style.color='var(--err)';btn.disabled=false;return;} - st.textContent=T.apd_success;st.style.color='var(--ok)'; - setTimeout(function(){location.reload();},2500); +function confirmAddPrinter() { + var ip = document.getElementById("apd-ip").value.trim(); + var name = document.getElementById("apd-name").value.trim(); + var st = document.getElementById("apd-status"), + btn = document.getElementById("apd-confirm"); + if (!ip) { + st.textContent = T.apd_err_ip; + st.style.color = "var(--err)"; + return; + } + st.textContent = T.apd_fetching; + st.style.color = "var(--txt2)"; + btn.disabled = true; + fetch("/kx/printers/add", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ printer_ip: ip, name: name }), + }) + .then(function (r) { + return r.json().then(function (j) { + return { ok: r.ok, j: j }; + }); }) - .catch(function(e){st.textContent=''+e;st.style.color='var(--err)';btn.disabled=false;}); -} -function removePrinter(id,name){ - if(!confirm(T.printers_remove_confirm.replace('{name}',name)))return; - fetch('/kx/printers/'+encodeURIComponent(id),{method:'DELETE'}) - .then(function(r){return r.json().then(function(j){return {ok:r.ok,j:j};});}) - .then(function(res){ - if(!res.ok){alert((res.j&&res.j.error)||tr('log_error','Fehler:'));return;} - setTimeout(function(){location.href='/printer1';},2000); + .then(function (res) { + if (!res.ok) { + st.textContent = (res.j && res.j.error) || tr("log_error", "Fehler:"); + st.style.color = "var(--err)"; + btn.disabled = false; + return; + } + st.textContent = T.apd_success; + st.style.color = "var(--ok)"; + setTimeout(function () { + location.reload(); + }, 2500); }) - .catch(function(e){alert(''+e);}); + .catch(function (e) { + st.textContent = "" + e; + st.style.color = "var(--err)"; + btn.disabled = false; + }); +} +function removePrinter(id, name) { + if (!confirm(T.printers_remove_confirm.replace("{name}", name))) return; + fetch("/kx/printers/" + encodeURIComponent(id), { method: "DELETE" }) + .then(function (r) { + return r.json().then(function (j) { + return { ok: r.ok, j: j }; + }); + }) + .then(function (res) { + if (!res.ok) { + alert((res.j && res.j.error) || tr("log_error", "Fehler:")); + return; + } + setTimeout(function () { + location.href = "/printer1"; + }, 2000); + }) + .catch(function (e) { + alert("" + e); + }); } // ── Drucker-Tab ── -function loadPrinterTab(){ - var grid=document.getElementById('printers-grid'); - if(grid)grid.innerHTML='
'+T.printers_loading+'
'; +function loadPrinterTab() { + var grid = document.getElementById("printers-grid"); + if (grid) + grid.innerHTML = + '
' + + T.printers_loading + + "
"; // Drucker-Liste von lokaler Instanz holen - fetch('/kx/printers').then(function(r){return r.json()}).then(function(d){ - var printers=d.result||[]; - if(!printers.length){ - if(grid)grid.innerHTML='
'+ - '
🖨
'+ - '
'+T.printers_empty_hint+'
'+ - ''+ - '
'; - return; - } - // Status jedes Druckers parallel abrufen - var fetches=printers.map(function(p){ - var url=(p.bridge_url||'').replace(/\/+$/,''); - return fetch(url+'/api/state',{signal:AbortSignal.timeout(3000)}) - .then(function(r){return r.json()}) - .then(function(s){return {printer:p,state:s,online:true};}) - .catch(function(){return {printer:p,state:{},online:false};}); + fetch("/kx/printers") + .then(function (r) { + return r.json(); + }) + .then(function (d) { + var printers = d.result || []; + if (!printers.length) { + if (grid) + grid.innerHTML = + '
' + + '
🖨
' + + '
' + + T.printers_empty_hint + + "
" + + '" + + "
"; + return; + } + // Status jedes Druckers parallel abrufen + var fetches = printers.map(function (p) { + var url = (p.bridge_url || "").replace(/\/+$/, ""); + return fetch(url + "/api/state", { signal: AbortSignal.timeout(3000) }) + .then(function (r) { + return r.json(); + }) + .then(function (s) { + return { printer: p, state: s, online: true }; + }) + .catch(function () { + return { printer: p, state: {}, online: false }; + }); + }); + Promise.all(fetches).then(function (results) { + var activeId = _activePrinter ? String(_activePrinter.id) : null; + if (grid) + grid.innerHTML = results + .map(function (res) { + var p = res.printer, + s = res.state, + online = res.online; + var isActive = String(p.id) === activeId; + var url = (p.bridge_url || "").replace(/\/+$/, ""); + var printerNum = p.id; + var ks = online ? s.kobra_state || "free" : "offline"; + var stateKey = "kobra_" + ks; + var stateLabel = T[stateKey] || ks; + var stateColor = + ks === "free" + ? "var(--ok)" + : ks === "printing" + ? "var(--accent)" + : ks === "offline" + ? "var(--txt2)" + : "var(--warn)"; + var progress = + online && s.progress ? Math.round(s.progress * 100) : null; + var filename = online && s.filename ? s.filename : ""; + var nt = online && s.nozzle_temp ? s.nozzle_temp.toFixed(1) : "–"; + var bt = online && s.bed_temp ? s.bed_temp.toFixed(1) : "–"; + var border = isActive + ? "2px solid var(--accent)" + : "1px solid var(--border)"; + var nameEsc = String(p.name) + .replace(/\\/g, "\\\\") + .replace(/'/g, "\\'"); + return ( + '
' + + '
' + + '🖨 ' + + p.name + + "" + + '' + + (isActive + ? '' + + T.printers_active + + "" + : "") + + "' + + "" + + "
" + + '
' + + '' + + '' + + stateLabel + + "" + + "
" + + (p.printer_ip + ? '
🌐 ' + + p.printer_ip + + "
" + : "") + + (filename + ? '
📄 ' + + filename + + "
" + : "") + + (progress !== null + ? '
' + : "") + + '
' + + "🌡 " + + nt + + "°C🛏 " + + bt + + "°C" + + "
" + + (!isActive + ? '' + + T.printers_switch + + "" + : '
' + + T.printers_current + + "
") + + "
" + ); + }) + .join(""); + }); + }) + .catch(function (e) { + if (grid) + grid.innerHTML = + '
' + + tr("log_error", "Fehler:") + + " " + + e + + "
"; }); - Promise.all(fetches).then(function(results){ - var activeId=_activePrinter?String(_activePrinter.id):null; - if(grid)grid.innerHTML=results.map(function(res){ - var p=res.printer,s=res.state,online=res.online; - var isActive=String(p.id)===activeId; - var url=(p.bridge_url||'').replace(/\/+$/,''); - var printerNum=p.id; - var ks=online?(s.kobra_state||'free'):'offline'; - var stateKey='kobra_'+ks; - var stateLabel=T[stateKey]||ks; - var stateColor=ks==='free'?'var(--ok)':ks==='printing'?'var(--accent)':ks==='offline'?'var(--txt2)':'var(--warn)'; - var progress=online&&s.progress?Math.round(s.progress*100):null; - var filename=online&&s.filename?s.filename:''; - var nt=online&&s.nozzle_temp?s.nozzle_temp.toFixed(1):'–'; - var bt=online&&s.bed_temp?s.bed_temp.toFixed(1):'–'; - var border=isActive?'2px solid var(--accent)':'1px solid var(--border)'; - var nameEsc=String(p.name).replace(/\\/g,'\\\\').replace(/'/g,"\\'"); - return '
'+ - '
'+ - '🖨 '+p.name+''+ - ''+ - (isActive?''+T.printers_active+'':'')+ - ''+ - ''+ - '
'+ - '
'+ - ''+ - ''+stateLabel+''+ - '
'+ - (p.printer_ip?'
🌐 '+p.printer_ip+'
':'')+ - (filename?'
📄 '+filename+'
':'')+ - (progress!==null?'
':'')+ - '
'+ - '🌡 '+nt+'°C🛏 '+bt+'°C'+ - '
'+ - (!isActive?''+T.printers_switch+'':'
'+T.printers_current+'
')+ - '
'; - }).join(''); - }); - }).catch(function(e){ - if(grid)grid.innerHTML='
'+tr('log_error','Fehler:')+' '+e+'
'; - }); } diff --git a/web/translations/de.json b/web/translations/de.json index 59f3c51..bd8c38c 100644 --- a/web/translations/de.json +++ b/web/translations/de.json @@ -1,328 +1,329 @@ { - "header_status_standby": "Bereit", - "header_status_printing": "Druckt", - "header_status_complete": "Fertig", - "header_status_error": "Fehler", - "kobra_free": "Bereit", - "kobra_busy": "Beschäftigt", - "kobra_printing": "Druckt", - "kobra_preheating": "Aufheizen", - "kobra_auto_leveling": "Nivellierung", - "kobra_checking": "Prüfung", - "kobra_updated": "Aktualisierung", - "kobra_init": "Initialisierung", - "kobra_pausing": "Pausiert...", - "kobra_paused": "Pausiert", - "kobra_resuming": "Fortsetzen...", - "kobra_resumed": "Fortgesetzt", - "kobra_stopping": "Stoppt...", - "kobra_stoped": "Gestoppt", - "kobra_finished": "Abgeschlossen", - "kobra_failed": "Fehler", - "kobra_canceled": "Abgebrochen", - "kobra_offline": "Offline", - "nav_dashboard": "Dashboard", - "nav_print": "Druck", - "nav_temps": "Temperaturen", - "nav_motion": "Achsen", - "nav_ams": "AMS", - "nav_extras": "Licht / Lüfter", - "nav_console": "Konsole", - "card_progress": "Fortschritt", - "card_temps": "Temperaturen", - "card_light_fan": "Lüfter", - "card_speed": "Druckgeschwindigkeit", - "card_cam": "Kamera", - "lbl_elapsed": "Verstrichen:", - "lbl_remaining": "Restzeit:", - "lbl_slicer_time": "Slicer-Schätzung:", - "lbl_layers": "Layer", - "lbl_zpos": "Z (mm)", - "speed_silent": "🐢 Leise", - "speed_normal": "⚡ Normal", - "speed_sport": "🚀 Sport", - "lbl_light": "💡 Licht", - "lbl_feed": "Einziehen", - "lbl_unload": "Ausziehen", - "card_ace_dry": "ACE Trocknung", + "ace_dry_auto_refill": "Auto-Nachschub", + "ace_dry_chart": "Verlauf (Temp/Feuchte)", + "ace_dry_current_temp": "Temperatur", + "ace_dry_dialog_cancel": "Abbrechen", + "ace_dry_dialog_confirm": "Bestätigen", + "ace_dry_dialog_custom_name": "Eigener Name", + "ace_dry_dialog_reset_default": "Auf Standard zurücksetzen", + "ace_dry_dialog_save_restart": "Speichern & Neustart", + "ace_dry_dialog_temp": "Temperatur (30-80°C)", + "ace_dry_dialog_time": "Restzeit (h:m:s)", + "ace_dry_dialog_title": "Trockner Temp/Zeit-Einstellungen", "ace_dry_dryer": "Trockner", + "ace_dry_duration": "Dauer (Min)", + "ace_dry_enable": "Trocknung aktivieren", + "ace_dry_humidity": "Luftfeuchte", + "ace_dry_preset_abs_asa": "ABS / ASA", + "ace_dry_preset_custom": "Custom", + "ace_dry_preset_pa_pc": "PA / PC", + "ace_dry_preset_petg": "PETG", + "ace_dry_preset_pla": "PLA", + "ace_dry_preset_pla_plus": "PLA+", + "ace_dry_preset_tpu": "TPU", + "ace_dry_start": "▶ Start", "ace_dry_status_off": "Status: Aus", "ace_dry_status_on": "Status: Aktiv", "ace_dry_status_remaining": "Rest", - "ace_dry_humidity": "Luftfeuchte", - "ace_dry_current_temp": "Temperatur", - "ace_dry_chart": "Verlauf (Temp/Feuchte)", - "ace_dry_temp": "Temperatur (°C)", - "ace_dry_duration": "Dauer (Min)", - "ace_dry_start": "▶ Start", "ace_dry_stop": "■ Stop", - "ace_dry_auto_refill": "Auto-Nachschub", - "ace_dry_enable": "Trocknung aktivieren", + "ace_dry_temp": "Temperatur (°C)", "ace_dry_temp_line": "Trocknungstemperatur", "ace_dry_time_line": "Trocknungszeit", "ace_dry_ui_pending": "(nur UI, Backend folgt)", - "ace_dry_dialog_title": "Trockner Temp/Zeit-Einstellungen", - "ace_dry_dialog_temp": "Temperatur (30-80°C)", - "ace_dry_dialog_time": "Restzeit (h:m:s)", - "ace_dry_dialog_confirm": "Bestätigen", - "ace_dry_dialog_cancel": "Abbrechen", - "ace_dry_dialog_save_restart": "Speichern & Neustart", - "ace_dry_dialog_custom_name": "Eigener Name", - "ace_dry_dialog_reset_default": "Auf Standard zurücksetzen", - "cam_placeholder": "📷 Kamera nicht gestartet", - "cam_stream_unavailable": "Stream nicht verfügbar", + "add_printer": "Drucker hinzufügen", + "ams_empty": "Leer", + "ams_no_data": "Keine AMS-Daten empfangen", + "apd_cancel": "Abbrechen", + "apd_confirm": "Hinzufügen", + "apd_err_ip": "Bitte IP-Adresse eingeben", + "apd_fetching": "Hole Daten vom Drucker…", + "apd_lbl_ip": "Drucker-IP", + "apd_lbl_name": "Name (optional)", + "apd_placeholder_name": "z.B. Kobra X Wohnzimmer", + "apd_success": "Drucker hinzugefügt, Bridge startet neu…", + "apd_title": "Drucker hinzufügen", "btn_cam_start": "▶ Kamera", + "btn_cam_start2": "▶ Start", "btn_cam_stop": "◼ Kamera", + "btn_cam_stop2": "◼ Stop", + "btn_cancel": "✕ Stopp", + "btn_cancel_generic": "Abbrechen", + "btn_confirm_generic": "Bestätigen", + "btn_connect": "⚡ Verbinden", + "btn_delete": "Löschen", + "btn_disable_motors": "Motoren aus", + "btn_disconnect": "✕ Trennen", + "btn_home_all": "Home All", + "btn_home_xy": "Home XY", + "btn_home_z": "Home Z", "btn_pause": "⏸ Pause", "btn_resume": "▶ Weiter", - "btn_cancel": "✕ Stopp", - "label_nozzle": "Düse", + "cam_placeholder": "📷 Kamera nicht gestartet", + "cam_stream_unavailable": "Stream nicht verfügbar", + "card_ace_dry": "ACE Trocknung", + "card_ams": "Filament", + "card_cam": "Kamera", + "card_light_fan": "Lüfter", + "card_progress": "Fortschritt", + "card_speed": "Druckgeschwindigkeit", + "card_temps": "Temperaturen", + "confirm_cancel": "Druck wirklich abbrechen?", + "fd_cancel": "Abbrechen", + "fd_no_matching_material": "Kein passendes Material", + "fd_no_slots_msg": "Keine belegten AMS-Slots.{br}Druck trotzdem starten?", + "fd_objects_hint": "Objekte überspringen (optional):", + "fd_objects_toggle": "Objekte überspringen", + "fd_options_title": "Optionen", + "fd_print": "▶ Drucken", + "fd_slot": "Slot", + "fd_slots_hint": "GCode-Kanal → AMS-Slot zuweisen:", + "fd_used": "BELEGT", + "file_cancel_btn": "✕ Abbrechen", + "file_ready_btn": "▶ Druck starten", + "file_slots_btn": "🎨 Slots wählen", + "header_status_complete": "Fertig", + "header_status_error": "Fehler", + "header_status_printing": "Druckt", + "header_status_standby": "Bereit", + "hint_ip_no_port": "Nur IP-Adresse, kein Port (z.B. 192.168.1.102)", + "kobra_auto_leveling": "Nivellierung", + "kobra_busy": "Beschäftigt", + "kobra_canceled": "Abgebrochen", + "kobra_checking": "Prüfung", + "kobra_failed": "Fehler", + "kobra_finished": "Abgeschlossen", + "kobra_free": "Bereit", + "kobra_init": "Initialisierung", + "kobra_offline": "Offline", + "kobra_paused": "Pausiert", + "kobra_pausing": "Pausiert...", + "kobra_preheating": "Aufheizen", + "kobra_printing": "Druckt", + "kobra_resumed": "Fortgesetzt", + "kobra_resuming": "Fortsetzen...", + "kobra_stoped": "Gestoppt", + "kobra_stopping": "Stoppt...", + "kobra_updated": "Aktualisierung", "label_bed": "Bett", "label_fan": "🌀 Lüfter", "label_light": "💡 Licht", - "label_on_off": "Ein / Aus", - "label_speed": "Geschwindigkeit", - "panel_print_title": "Drucksteuerung", - "panel_print_btn_pause": "⏸ Pause", - "panel_print_btn_resume": "▶ Fortsetzen", - "panel_print_btn_cancel": "✕ Abbrechen", - "panel_print_temps_live": "Temperaturen (Live)", - "label_set": "Setzen", + "label_nozzle": "Düse", "label_off": "Aus", - "panel_temps_nozzle": "Düse", - "panel_temps_bed": "Heizbett", - "panel_temps_chart": "Verlauf (letzte 60 Messungen)", - "label_target_c": "Ziel:", - "panel_motion_xy": "XY-Achsen", - "panel_motion_z": "Z-Achse", - "label_step": "Schrittweite:", - "btn_home_z": "Home Z", - "btn_home_xy": "Home XY", - "btn_home_all": "Home All", - "btn_disable_motors": "Motoren aus", - "panel_ams_title": "Filament", - "card_ams": "Filament", - "ams_no_data": "Keine AMS-Daten empfangen", + "label_on_off": "Ein / Aus", + "label_set": "Setzen", "label_slot": "Slot", - "ams_empty": "Leer", - "panel_extras_light": "Licht", - "panel_extras_fan": "Lüfter", - "panel_extras_camera": "Kamera", - "btn_cam_start2": "▶ Start", - "btn_cam_stop2": "◼ Stop", - "panel_console_title": "Ereignis-Log", - "log_light_on": "Licht an", - "log_light_off": "Licht aus", - "log_fan": "Lüfter →", - "log_nozzle": "Düse →", - "log_bed": "Bett →", + "label_speed": "Geschwindigkeit", + "label_step": "Schrittweite:", + "label_target_c": "Ziel:", + "lbl_conn_error": "Verbindungsfehler:", + "lbl_elapsed": "Verstrichen:", + "lbl_feed": "Einziehen", + "lbl_layers": "Layer", + "lbl_light": "💡 Licht", + "lbl_remaining": "Restzeit:", + "lbl_slicer_time": "Slicer-Schätzung:", + "lbl_unload": "Ausziehen", + "lbl_zpos": "Z (mm)", + "log_auto": "⬇ Auto", "log_axis": "Achse", - "log_home": "Home", - "log_home_all": "Home All", + "log_bed": "Bett →", "log_cam_start": "Kamera gestartet:", "log_cam_stop": "Kamera gestoppt", - "log_poll_error": "Poll-Fehler:", + "log_clear": "✕ Leeren", + "log_delete_failed": "Löschung fehlgeschlagen", + "log_dir_all": "Alle", + "log_dir_label": "Richtung:", + "log_dir_rx": "RX", + "log_dir_tx": "TX", + "log_download": "⬇ Download", "log_error": "Fehler:", - "confirm_cancel": "Druck wirklich abbrechen?", - "settings_title": "Einstellungen", - "settings_connection": "Verbindung", - "settings_print": "Druckeinstellungen", - "settings_poll": "Poll-Intervall (Sekunden)", - "settings_version": "Version", + "log_fan": "Lüfter →", + "log_filter_placeholder": "Filtern…", + "log_home": "Home", + "log_home_all": "Home All", + "log_light_off": "Licht aus", + "log_light_on": "Licht an", + "log_lvl_err": "⛔ Fehler", + "log_lvl_label": "Level:", + "log_lvl_warn": "⚠ Warnung", + "log_nozzle": "Düse →", + "log_poll_error": "Poll-Fehler:", + "log_print_action": "Druck:", + "log_print_start": "Druckstart:", + "log_topic_ams": "AMS", + "log_topic_info": "Info", + "log_topic_label": "Thema:", + "log_topic_print": "Druck", + "log_topic_status": "Status", + "nav_ams": "AMS", + "nav_browser": "Browser", + "nav_console": "Konsole", + "nav_dashboard": "Dashboard", + "nav_extras": "Licht / Lüfter", + "nav_motion": "Achsen", + "nav_print": "Druck", + "nav_printers": "Drucker", "nav_settings": "Einstellungen", - "settings_cat_display": "Darstellung", - "settings_cat_filament": "Filament", - "settings_cat_language": "Sprache", - "settings_cat_theme": "Hell / Dunkel umschalten", - "settings_filament_mapping": "Filament-Profil-Mapping (pro Slot)", - "settings_filament_mapping_save": "Mapping speichern", - "settings_visible_vendors": "Sichtbare Hersteller (Profil-Dropdown)", - "settings_visible_vendors_hint": "Nur diese Hersteller erscheinen im Slot-Profil-Dropdown. Nichts ausgewählt = alle anzeigen. „Generic\" und eigene Profile sind immer sichtbar.", - "settings_visible_vendors_save": "Auswahl speichern", - "progress_action_print": "Drucken", - "progress_action_slots": "Slots zuordnen", - "progress_action_clear": "Leeren", - "settings_save": "Speichern & Neustart", - "settings_printer_name": "Drucker-Name", - "settings_printer_ip": "Drucker-IP", - "settings_mqtt_port": "MQTT-Port", - "settings_username": "MQTT-Benutzername", - "settings_password": "MQTT-Passwort", - "settings_device_id": "Device-ID", - "settings_mode_id": "Mode-ID", - "hint_ip_no_port": "Nur IP-Adresse, kein Port (z.B. 192.168.1.102)", - "settings_default_slot": "Standard-Slot (Einfarbdruck)", - "settings_slot_auto": "Auto (alle belegten Slots)", - "settings_auto_leveling": "Auto-Leveling vor Druck", - "settings_camera_on_print": "Kamera bei Druckstart einschalten", - "settings_web_upload_warning": "Warnung bei Web-Upload-Druck anzeigen", - "update_check": "Auf Updates prüfen", - "update_checking": "Prüfe...", - "update_available": "verfügbar", - "update_none": "Bereits aktuell", - "update_apply": "Jetzt installieren", - "update_applying": "Lade herunter...", - "update_restarting": "Starte neu...", - "update_error": "Fehler", - "btn_connect": "⚡ Verbinden", - "btn_disconnect": "✕ Trennen", - "lbl_conn_error": "Verbindungsfehler:", - "slot_edit_title": "Slot bearbeiten", - "slot_edit_color": "Farbe", - "slot_edit_material": "Material", - "slot_edit_load": "⬇ Einziehen", - "slot_edit_unload": "⬆ Ausziehen", - "slot_edit_save": "💾 Speichern", - "slot_edit_custom": "z.B. PLA, PETG, ABS…", - "slot_edit_ok": "AMS Slot", - "slot_edit_profile": "OrcaSlicer-Profil", - "slot_edit_profile_hint": "Sendet beim OrcaSlicer-Sync die konkrete Marke statt nur „Generic\"", - "slot_edit_profile_default": "— Generic (Default) —", - "orca_profile_section": "OrcaSlicer-Profile", + "nav_temps": "Temperaturen", + "orca_profile_done": "Importiert", + "orca_profile_dropmsg": "Hierher ziehen oder klicken", + "orca_profile_help_html": "Lade ein ZIP deines OrcaSlicer-Filament-Ordners oder einzelne .json-Files hoch.
In OrcaSlicer: Help → Show Configuration Folder → user/<id>/filament/", "orca_profile_hint": "Eigene Profile aus OrcaSlicer importieren (User-Dir öffnen via Help → Show Configuration Folder)", "orca_profile_import_btn": "Profile importieren", "orca_profile_import_link": "★ Eigene Profile importieren…", "orca_profile_import_title": "Eigene OrcaSlicer-Profile importieren", - "orca_profile_help_html": "Lade ein ZIP deines OrcaSlicer-Filament-Ordners oder einzelne .json-Files hoch.
In OrcaSlicer: Help → Show Configuration Folder → user/<id>/filament/", - "orca_profile_dropmsg": "Hierher ziehen oder klicken", "orca_profile_list_label": "Aktuell importiert", - "orca_profile_user_label": "Eigene Profile", - "orca_profile_user_empty": "– keine –", - "orca_profile_uploading": "Lade hoch…", - "orca_profile_done": "Importiert", + "orca_profile_section": "OrcaSlicer-Profile", "orca_profile_skipped": "übersprungen", - "log_dir_all": "Alle", - "log_lvl_label": "Level:", - "file_ready_btn": "▶ Druck starten", - "file_slots_btn": "🎨 Slots wählen", - "file_cancel_btn": "✕ Abbrechen", - "nav_printers": "Drucker", - "skip_title": "✂ Objekte überspringen", - "skip_hint": "Objekte abwählen, die nicht weiter gedruckt werden sollen:", - "skip_btn_label": "Objekte", - "skip_no_objects": "Keine Objekte in diesem Druck.", + "orca_profile_uploading": "Lade hoch…", + "orca_profile_user_empty": "– keine –", + "orca_profile_user_label": "Eigene Profile", + "panel_ams_title": "Filament", + "panel_browser_title": "Datei-Browser", + "panel_console_title": "Ereignis-Log", + "panel_extras_camera": "Kamera", + "panel_extras_fan": "Lüfter", + "panel_extras_light": "Licht", + "panel_motion_xy": "XY-Achsen", + "panel_motion_z": "Z-Achse", + "panel_print_btn_cancel": "✕ Abbrechen", + "panel_print_btn_pause": "⏸ Pause", + "panel_print_btn_resume": "▶ Fortsetzen", + "panel_print_temps_live": "Temperaturen (Live)", + "panel_print_title": "Drucksteuerung", + "panel_temps_bed": "Heizbett", + "panel_temps_chart": "Verlauf (letzte 60 Messungen)", + "panel_temps_nozzle": "Düse", + "print_auto_leveling": "Auto-Leveling für diesen Druck", + "printers_active": "● aktiv", + "printers_current": "Aktueller Drucker", + "printers_empty_hint": "Noch kein Drucker eingerichtet.", + "printers_loading": "Lade…", + "printers_none": "Keine Drucker konfiguriert.", + "printers_remove": "Drucker entfernen", + "printers_remove_confirm": "Drucker \"{name}\" entfernen? Die Bridge startet neu.", + "printers_switch": "Wechseln →", + "progress_action_clear": "Leeren", + "progress_action_print": "Drucken", + "progress_action_slots": "Slots zuordnen", + "settings_auto_leveling": "Auto-Leveling vor Druck", + "settings_btn_tooltip": "Einstellungen", + "settings_camera_on_print": "Kamera bei Druckstart einschalten", + "settings_cat_display": "Darstellung", + "settings_cat_filament": "Filament", + "settings_cat_language": "Sprache", + "settings_cat_notifications": "Benachrichtigungen", + "settings_cat_system": "System", + "settings_cat_theme": "Hell / Dunkel umschalten", + "settings_connection": "Verbindung", + "settings_default_slot": "Standard-Slot (Einfarbdruck)", + "settings_device_id": "Device-ID", + "settings_device_id_hint": "32 Hexzeichen", + "settings_device_id_placeholder": "32 Hexzeichen", + "settings_filament_mapping": "Filament-Profil-Mapping (pro Slot)", + "settings_filament_mapping_save": "Mapping speichern", + "settings_file_ready_banner": "Druckleiste", + "settings_file_ready_dialog": "Druckdialog", + "settings_file_ready_mode": "Nach Upload: Druckstart-Verhalten", + "settings_language": "Sprache", + "settings_mode_id": "Mode-ID", + "settings_mode_id_placeholder": "20030", + "settings_mqtt_port": "MQTT-Port", + "settings_mqtt_username_placeholder": "userXXXXXXXX", + "settings_notif_add": "Benachrichtigung hinzufügen", + "settings_notif_empty": "Keine Benachrichtigungen konfiguriert.", + "settings_notif_ev_cancelled": "Abgebrochen", + "settings_notif_ev_failed": "Fehler", + "settings_notif_ev_finished": "Fertig", + "settings_notif_ev_paused": "Pausiert", + "settings_notif_ev_progress": "Fortschritt", + "settings_notif_ev_started": "Gestartet", + "settings_notif_interval_lbl": "Wiederholungsintervall", + "settings_notif_layers_unit": "Schichten", + "settings_notif_min_unit": "Min", + "settings_notif_send_image": "Bild", + "settings_notif_test": "Test", + "settings_notif_test_fail": "Fehler", + "settings_notif_test_ok": "Gesendet", + "settings_notif_zero_off": "(0 = aus)", + "settings_notifications": "Benachrichtigungen", + "settings_notifications_hint": "Sende Benachrichtigungen über Apprise-URLs (discord://, telegram://, gotify://, slack://, …)", + "settings_orca_profiles_import": "Profile importieren", + "settings_orca_profiles_label": "OrcaSlicer-Profile", + "settings_password": "MQTT-Passwort", + "settings_poll": "Poll-Intervall (Sekunden)", + "settings_print": "Druckeinstellungen", + "settings_printer_ip": "Drucker-IP", + "settings_printer_name": "Drucker-Name", + "settings_printer_name_placeholder": "z.B. Kobra X Links", + "settings_save": "Speichern & Neustart", + "settings_slot_auto": "Auto (alle belegten Slots)", + "settings_theme_toggle": "Wechsel Hell / Dunkel", + "settings_title": "Einstellungen", + "settings_username": "MQTT-Benutzername", + "settings_version": "Version", + "settings_visible_vendors": "Sichtbare Hersteller (Profil-Dropdown)", + "settings_visible_vendors_hint": "Nur diese Hersteller erscheinen im Slot-Profil-Dropdown. Nichts ausgewählt = alle anzeigen. „Generic\" und eigene Profile sind immer sichtbar.", + "settings_visible_vendors_save": "Auswahl speichern", + "settings_web_upload_warning": "Warnung bei Web-Upload-Druck anzeigen", + "sf_all": "Alle", + "sf_err": "✗ Fehler", + "sf_new": "Neu", + "sf_ok": "✓ Erfolgreich", "skip_already": "übersprungen", + "skip_btn_label": "Objekte", + "skip_cancel": "Abbrechen", + "skip_confirm": "Überspringen", + "skip_confirm_btn": "Überspringen", + "skip_hint": "Objekte abwählen, die nicht weiter gedruckt werden sollen:", + "skip_no_objects": "Keine Objekte in diesem Druck.", "skip_select_at_least_one": "Bitte mindestens ein Objekt wählen.", "skip_sending": "Sende …", "skip_success": "Objekte werden übersprungen.", - "fd_objects_hint": "Objekte überspringen (optional):", - "fd_objects_toggle": "Objekte überspringen", - "fd_slots_hint": "GCode-Kanal → AMS-Slot zuweisen:", - "fd_cancel": "Abbrechen", - "fd_print": "▶ Drucken", - "fd_no_slots_msg": "Keine belegten AMS-Slots.{br}Druck trotzdem starten?", - "fd_slot": "Slot", - "fd_no_matching_material": "Kein passendes Material", - "fd_used": "BELEGT", - "add_printer": "Drucker hinzufügen", - "apd_lbl_ip": "Drucker-IP", - "apd_lbl_name": "Name (optional)", - "apd_placeholder_name": "z.B. Kobra X Wohnzimmer", - "apd_cancel": "Abbrechen", - "apd_confirm": "Hinzufügen", - "apd_fetching": "Hole Daten vom Drucker…", - "apd_success": "Drucker hinzugefügt, Bridge startet neu…", - "apd_err_ip": "Bitte IP-Adresse eingeben", - "printers_remove": "Drucker entfernen", - "printers_remove_confirm": "Drucker \"{name}\" entfernen? Die Bridge startet neu.", - "printers_active": "● aktiv", - "printers_switch": "Wechseln →", - "printers_current": "Aktueller Drucker", - "printers_loading": "Lade…", - "printers_none": "Keine Drucker konfiguriert.", - "printers_empty_hint": "Noch kein Drucker eingerichtet.", - "nav_browser": "Browser", - "panel_browser_title": "Datei-Browser", - "store_search_placeholder": "🔍 Suche…", - "store_empty": "Noch keine Dateien hochgeladen.", - "store_refresh": "↻ Aktualisieren", - "store_print": "▶ Drucken", - "store_download": "⬇ Download", - "store_delete_confirm": "Datei löschen?", - "store_print_confirm": "Datei drucken?", - "store_web_verify_title": "Datei verifizieren", - "store_web_verify_msg": "Bitte bestätige, dass diese Datei für den Anycubic Kobra X erstellt wurde.", - "store_web_verify_confirm": "Bestätigen", - "store_web_verify_abort": "Abbrechen", - "store_no_results": "Keine Dateien gefunden.", - "store_never": "noch nicht gedruckt", - "store_estimate": "Schätzung", - "store_upload_label_prefix": "GCode hierher ziehen oder ", - "store_upload_label_browse": "durchsuchen", - "store_upload_busy": "⏳ Hochladen…", - "store_upload_success": "✓ {file}", - "store_upload_error": "✗ {error}", - "store_upload_only_gcode": "✗ Nur GCode-Dateien erlaubt (.gcode, .3mf, .bgcode)", - "sf_all": "Alle", - "sf_ok": "✓ Erfolgreich", - "sf_err": "✗ Fehler", - "sf_new": "Neu", + "skip_title": "✂ Objekte überspringen", + "slot_edit_color": "Farbe", + "slot_edit_custom": "z.B. PLA, PETG, ABS…", + "slot_edit_load": "⬇ Einziehen", + "slot_edit_material": "Material", + "slot_edit_ok": "AMS Slot", + "slot_edit_profile": "OrcaSlicer-Profil", + "slot_edit_profile_default": "— Generic (Default) —", + "slot_edit_profile_hint": "Sendet beim OrcaSlicer-Sync die konkrete Marke statt nur „Generic\"", + "slot_edit_save": "💾 Speichern", + "slot_edit_title": "Slot bearbeiten", + "slot_edit_unload": "⬆ Ausziehen", + "speed_normal": "⚡ Normal", + "speed_silent": "🐢 Leise", + "speed_sport": "🚀 Sport", "ss_date": "↓ Datum", - "ss_name": "A–Z Name", "ss_dur": "⏱ Druckzeit", - "ace_dry_preset_pla": "PLA", - "ace_dry_preset_pla_plus": "PLA+", - "ace_dry_preset_petg": "PETG", - "ace_dry_preset_tpu": "TPU", - "ace_dry_preset_abs_asa": "ABS / ASA", - "ace_dry_preset_pa_pc": "PA / PC", - "ace_dry_preset_custom": "Custom", - "fd_options_title": "Optionen", - "print_auto_leveling": "Auto-Leveling für diesen Druck", - "settings_file_ready_mode": "Druckdialog starten", - "settings_file_ready_banner": "Druckleiste", - "settings_file_ready_dialog": "Druckdialog", - "log_dir_rx": "RX", - "log_dir_tx": "TX", - "log_dir_label": "Richtung:", - "log_lvl_err": "⛔ Fehler", - "log_lvl_warn": "⚠ Warnung", - "log_topic_label": "Thema:", - "log_topic_ams": "AMS", - "log_topic_print": "Druck", - "log_topic_info": "Info", - "log_topic_status": "Status", - "log_download": "⬇ Download", - "log_auto": "⬇ Auto", - "log_clear": "✕ Leeren", - "log_filter_placeholder": "Filtern…", - "skip_cancel": "Abbrechen", - "skip_confirm": "Überspringen", - "settings_cat_connection": "Verbindung", - "settings_cat_printer": "Drucker", - "settings_cat_notifications": "Benachrichtigungen", - "settings_cat_system": "System", - "settings_notifications": "Benachrichtigungen", - "settings_notifications_hint": "Sende Benachrichtigungen über Apprise-URLs (discord://, telegram://, gotify://, slack://, …)", - "settings_notif_add": "Benachrichtigung hinzufügen", - "settings_notif_empty": "Keine Benachrichtigungen konfiguriert.", - "settings_notif_test": "Test", - "settings_notif_test_ok": "Gesendet", - "settings_notif_test_fail": "Fehler", - "settings_notif_ev_finished": "Fertig", - "settings_notif_ev_failed": "Fehler", - "settings_notif_ev_cancelled": "Abgebrochen", - "settings_notif_ev_paused": "Pausiert", - "settings_notif_ev_started": "Gestartet", - "settings_notif_ev_progress": "Fortschritt", - "settings_notif_interval_lbl": "Wiederholungsintervall", - "settings_notif_min_unit": "Min", - "settings_notif_layers_unit": "Schichten", - "settings_notif_zero_off": "(0 = aus)", - "settings_notif_send_image": "Bild", - "settings_btn_tooltip": "Einstellungen", - "settings_printer_name_placeholder": "z.B. Kobra X Links", - "settings_device_id_placeholder": "32 Hexzeichen", - "settings_mqtt_username_placeholder": "userXXXXXXXX", - "settings_device_id_hint": "32 Hexzeichen", - "settings_mode_id_placeholder": "20030", - "settings_language": "Sprache", - "settings_theme_toggle": "Wechsel Hell / Dunkel", - "settings_orca_profiles_label": "OrcaSlicer-Profile", - "settings_orca_profiles_import": "Profile importieren", - "skip_confirm_btn": "Überspringen", - "btn_delete": "Löschen", - "log_print_start": "Druckstart:", - "log_print_action": "Druck:", - "log_delete_failed": "Löschung fehlgeschlagen" + "ss_name": "A–Z Name", + "store_delete_confirm": "Datei löschen?", + "store_download": "⬇ Download", + "store_empty": "Noch keine Dateien hochgeladen.", + "store_estimate": "Schätzung", + "store_never": "noch nicht gedruckt", + "store_no_results": "Keine Dateien gefunden.", + "store_print": "▶ Drucken", + "store_print_confirm": "Datei drucken?", + "store_refresh": "↻ Aktualisieren", + "store_search_placeholder": "🔍 Suche…", + "store_upload_busy": "⏳ Hochladen…", + "store_upload_error": "✗ {error}", + "store_upload_label_browse": "durchsuchen", + "store_upload_label_prefix": "GCode hierher ziehen oder ", + "store_upload_only_gcode": "✗ Nur GCode-Dateien erlaubt (.gcode, .3mf, .bgcode)", + "store_upload_success": "✓ {file}", + "store_web_verify_abort": "Abbrechen", + "store_web_verify_confirm": "Bestätigen", + "store_web_verify_msg": "Bitte bestätige, dass diese Datei für den Anycubic Kobra X erstellt wurde.", + "store_web_verify_title": "Datei verifizieren", + "update_apply": "Jetzt installieren", + "update_applying": "Lade herunter...", + "update_available": "verfügbar", + "update_check": "Auf Updates prüfen", + "update_checking": "Prüfe...", + "update_error": "Fehler", + "update_none": "Bereits aktuell", + "update_restarting": "Starte neu..." } diff --git a/web/translations/en.json b/web/translations/en.json index e5f16b9..8a3a52b 100644 --- a/web/translations/en.json +++ b/web/translations/en.json @@ -1,335 +1,338 @@ { - "header_status_standby": "Ready", - "header_status_printing": "Printing", - "header_status_complete": "Complete", - "header_status_error": "Error", - "kobra_free": "Ready", - "kobra_busy": "Busy", - "kobra_printing": "Printing", - "kobra_preheating": "Preheating", - "kobra_auto_leveling": "Auto Leveling", - "kobra_checking": "Checking", - "kobra_updated": "Updating", - "kobra_init": "Initializing", - "kobra_pausing": "Pausing...", - "kobra_paused": "Paused", - "kobra_resuming": "Resuming...", - "kobra_resumed": "Resumed", - "kobra_stopping": "Stopping...", - "kobra_stoped": "Stopped", - "kobra_finished": "Finished", - "kobra_failed": "Error", - "kobra_canceled": "Cancelled", - "kobra_offline": "Offline", - "nav_dashboard": "Dashboard", - "nav_print": "Print", - "nav_temps": "Temperatures", - "nav_motion": "Motion", - "nav_ams": "AMS", - "nav_extras": "Light / Fan", - "nav_console": "Console", - "card_progress": "Progress", - "card_temps": "Temperatures", - "card_light_fan": "Fan", - "card_speed": "Print Speed", - "card_cam": "Camera", - "lbl_elapsed": "Elapsed:", - "lbl_remaining": "Remaining:", - "lbl_slicer_time": "Slicer estimate:", - "lbl_layers": "Layer", - "lbl_zpos": "Z (mm)", - "speed_silent": "🐢 Silent", - "speed_normal": "⚡ Normal", - "speed_sport": "🚀 Sport", - "lbl_light": "💡 Light", - "lbl_feed": "Load", - "lbl_unload": "Unload", - "card_ace_dry": "ACE Drying", + "ace_dry_auto_refill": "Auto Refill", + "ace_dry_chart": "History (Temp/Humidity)", + "ace_dry_current_temp": "Temperature", + "ace_dry_dialog_cancel": "Cancel", + "ace_dry_dialog_confirm": "Confirm", + "ace_dry_dialog_custom_name": "Custom Name", + "ace_dry_dialog_reset_default": "Reset to Default", + "ace_dry_dialog_save_restart": "Save & Restart", + "ace_dry_dialog_temp": "Temperature (30-80°C)", + "ace_dry_dialog_time": "Rem. Time (h:m:s)", + "ace_dry_dialog_title": "Dryer Temp/Time Settings", "ace_dry_dryer": "Dryer", + "ace_dry_duration": "Duration (min)", + "ace_dry_enable": "Enable Drying", + "ace_dry_humidity": "Humidity", + "ace_dry_preset_abs_asa": "ABS / ASA", + "ace_dry_preset_custom": "Custom", + "ace_dry_preset_pa_pc": "PA / PC", + "ace_dry_preset_petg": "PETG", + "ace_dry_preset_pla": "PLA", + "ace_dry_preset_pla_plus": "PLA+", + "ace_dry_preset_tpu": "TPU", + "ace_dry_start": "▶ Start", "ace_dry_status_off": "Status: Off", "ace_dry_status_on": "Status: Active", "ace_dry_status_remaining": "Remaining", - "ace_dry_humidity": "Humidity", - "ace_dry_current_temp": "Temperature", - "ace_dry_chart": "History (Temp/Humidity)", - "ace_dry_temp": "Temperature (°C)", - "ace_dry_duration": "Duration (min)", - "ace_dry_start": "▶ Start", "ace_dry_stop": "■ Stop", - "ace_dry_auto_refill": "Auto Refill", - "ace_dry_enable": "Enable Drying", + "ace_dry_temp": "Temperature (°C)", "ace_dry_temp_line": "Drying Temperature", "ace_dry_time_line": "Drying Time", "ace_dry_ui_pending": "(UI only, backend next)", - "ace_dry_dialog_title": "Dryer Temp/Time Settings", - "ace_dry_dialog_temp": "Temperature (30-80°C)", - "ace_dry_dialog_time": "Rem. Time (h:m:s)", - "ace_dry_dialog_confirm": "Confirm", - "ace_dry_dialog_cancel": "Cancel", - "ace_dry_dialog_save_restart": "Save & Restart", - "ace_dry_dialog_custom_name": "Custom Name", - "ace_dry_dialog_reset_default": "Reset to Default", - "ace_dry_preset_pla": "PLA", - "ace_dry_preset_pla_plus": "PLA+", - "ace_dry_preset_petg": "PETG", - "ace_dry_preset_tpu": "TPU", - "ace_dry_preset_abs_asa": "ABS / ASA", - "ace_dry_preset_pa_pc": "PA / PC", - "ace_dry_preset_custom": "Custom", - "cam_placeholder": "📷 Camera not started", - "cam_stream_unavailable": "Stream unavailable", + "add_printer": "Add printer", + "ams_empty": "Empty", + "ams_no_data": "No AMS data received", + "apd_cancel": "Cancel", + "apd_confirm": "Add", + "apd_err_ip": "Please enter an IP address", + "apd_fetching": "Fetching data from printer…", + "apd_lbl_ip": "Printer IP", + "apd_lbl_name": "Name (optional)", + "apd_placeholder_name": "e.g. Kobra X Living Room", + "apd_success": "Printer added, bridge restarting…", + "apd_title": "Add printer", "btn_cam_start": "▶ Camera", + "btn_cam_start2": "▶ Start", "btn_cam_stop": "◼ Camera", + "btn_cam_stop2": "◼ Stop", + "btn_cancel": "✕ Stop", + "btn_cancel_generic": "Cancel", + "btn_confirm_generic": "Confirm", + "btn_connect": "⚡ Connect", + "btn_delete": "Delete", + "btn_disable_motors": "Motors Off", + "btn_disconnect": "✕ Disconnect", + "btn_home_all": "Home All", + "btn_home_xy": "Home XY", + "btn_home_z": "Home Z", "btn_pause": "⏸ Pause", "btn_resume": "▶ Resume", - "btn_cancel": "✕ Stop", - "label_nozzle": "Nozzle", + "cam_placeholder": "📷 Camera not started", + "cam_stream_unavailable": "Stream unavailable", + "card_ace_dry": "ACE Drying", + "card_ams": "Filament", + "card_cam": "Camera", + "card_light_fan": "Fan", + "card_progress": "Progress", + "card_speed": "Print Speed", + "card_temps": "Temperatures", + "confirm_cancel": "Really cancel the print?", + "fd_cancel": "Cancel", + "fd_no_matching_material": "No matching material", + "fd_no_slots_msg": "No loaded AMS slots.{br}Start print anyway?", + "fd_objects_hint": "Skip objects (optional):", + "fd_objects_toggle": "Skip objects", + "fd_options_title": "Print Options", + "fd_print": "▶ Print", + "fd_slot": "Slot", + "fd_slots_hint": "Assign GCode channel to AMS slot:", + "fd_used": "USED", + "file_cancel_btn": "✕ Cancel", + "file_ready_btn": "▶ Start Print", + "file_slots_btn": "🎨 Select Slots", + "header_status_complete": "Complete", + "header_status_error": "Error", + "header_status_printing": "Printing", + "header_status_standby": "Ready", + "hint_ip_no_port": "IP address only, no port (e.g. 192.168.1.102)", + "kobra_auto_leveling": "Auto Leveling", + "kobra_busy": "Busy", + "kobra_canceled": "Cancelled", + "kobra_checking": "Checking", + "kobra_failed": "Error", + "kobra_finished": "Finished", + "kobra_free": "Ready", + "kobra_init": "Initializing", + "kobra_offline": "Offline", + "kobra_paused": "Paused", + "kobra_pausing": "Pausing...", + "kobra_preheating": "Preheating", + "kobra_printing": "Printing", + "kobra_resumed": "Resumed", + "kobra_resuming": "Resuming...", + "kobra_stoped": "Stopped", + "kobra_stopping": "Stopping...", + "kobra_updated": "Updating", "label_bed": "Bed", "label_fan": "🌀 Fan", "label_light": "💡 Light", - "label_on_off": "On / Off", - "label_speed": "Speed", - "panel_print_title": "Print Control", - "panel_print_btn_pause": "⏸ Pause", - "panel_print_btn_resume": "▶ Resume", - "panel_print_btn_cancel": "✕ Cancel", - "panel_print_temps_live": "Temperatures (Live)", - "label_set": "Set", + "label_nozzle": "Nozzle", "label_off": "Off", - "panel_temps_nozzle": "Nozzle", - "panel_temps_bed": "Heated Bed", - "panel_temps_chart": "History (last 60 readings)", - "label_target_c": "Target:", - "panel_motion_xy": "XY Axes", - "panel_motion_z": "Z Axis", - "label_step": "Step size:", - "btn_home_z": "Home Z", - "btn_home_xy": "Home XY", - "btn_home_all": "Home All", - "btn_disable_motors": "Motors Off", - "panel_ams_title": "Filament", - "card_ams": "Filament", - "ams_no_data": "No AMS data received", + "label_on_off": "On / Off", + "label_set": "Set", "label_slot": "Slot", - "ams_empty": "Empty", - "panel_extras_light": "Light", - "panel_extras_fan": "Fan", - "panel_extras_camera": "Camera", - "btn_cam_start2": "▶ Start", - "btn_cam_stop2": "◼ Stop", - "panel_console_title": "Event Log", - "log_light_on": "Light on", - "log_light_off": "Light off", - "log_fan": "Fan →", - "log_nozzle": "Nozzle →", - "log_bed": "Bed →", + "label_speed": "Speed", + "label_step": "Step size:", + "label_target_c": "Target:", + "lbl_conn_error": "Connection error:", + "lbl_elapsed": "Elapsed:", + "lbl_feed": "Load", + "lbl_layers": "Layer", + "lbl_light": "💡 Light", + "lbl_remaining": "Remaining:", + "lbl_slicer_time": "Slicer estimate:", + "lbl_unload": "Unload", + "lbl_zpos": "Z (mm)", + "log_auto": "⬇ Auto", "log_axis": "Axis", - "log_home": "Home", - "log_home_all": "Home All", + "log_bed": "Bed →", "log_cam_start": "Camera started:", "log_cam_stop": "Camera stopped", - "log_poll_error": "Poll error:", + "log_clear": "✕ Clear", + "log_delete_failed": "Delete failed", + "log_dir_all": "All", + "log_dir_label": "Dir:", + "log_dir_rx": "RX", + "log_dir_tx": "TX", + "log_download": "⬇ Download", "log_error": "Error:", - "confirm_cancel": "Really cancel the print?", - "settings_title": "Settings", - "settings_connection": "Connection", - "settings_print": "Print Settings", - "settings_poll": "Poll Interval (seconds)", + "log_fan": "Fan →", + "log_filter_placeholder": "Filter…", + "log_home": "Home", + "log_home_all": "Home All", + "log_light_off": "Light off", + "log_light_on": "Light on", + "log_lvl_err": "⛔ Errors", + "log_lvl_label": "Level:", + "log_lvl_warn": "⚠ Warn", + "log_nozzle": "Nozzle →", + "log_poll_error": "Poll error:", + "log_print_action": "Print:", + "log_print_start": "Print start:", + "log_topic_ams": "AMS", + "log_topic_info": "Info", + "log_topic_label": "Topic:", + "log_topic_print": "Print", + "log_topic_status": "Status", + "nav_ams": "AMS", + "nav_browser": "Browser", + "nav_console": "Console", + "nav_dashboard": "Dashboard", + "nav_extras": "Light / Fan", + "nav_motion": "Motion", + "nav_print": "Print", + "nav_printers": "Printers", "nav_settings": "Settings", - "settings_filament_mapping": "Filament profile mapping (per slot)", - "settings_filament_mapping_save": "Save mapping", - "settings_visible_vendors": "Visible vendors (profile dropdown)", - "settings_visible_vendors_hint": "Only these vendors appear in the slot profile dropdown. Nothing selected = show all. \"Generic\" and your own profiles are always visible.", - "settings_visible_vendors_save": "Save selection", - "progress_action_print": "Print", - "progress_action_slots": "Map slots", - "progress_action_clear": "Clear", - "settings_version": "Version", - "settings_save": "Save & Restart", - "settings_printer_name": "Printer Name", - "settings_printer_ip": "Printer IP", - "settings_mqtt_port": "MQTT Port", - "settings_username": "MQTT Username", - "settings_password": "MQTT Password", - "settings_device_id": "Device ID", - "settings_mode_id": "Mode ID", - "hint_ip_no_port": "IP address only, no port (e.g. 192.168.1.102)", - "settings_default_slot": "Default Slot (single color)", - "settings_slot_auto": "Auto (all loaded slots)", - "settings_auto_leveling": "Auto-Leveling Default", - "settings_cat_connection": "Connection", - "settings_cat_printer": "Printer", - "settings_cat_display": "Appearance", - "settings_cat_filament": "Filament", - "settings_cat_notifications": "Notifications", - "settings_cat_system": "System", - "settings_auto_leveling_label": "Auto-Leveling before print", - "settings_poll_interval_label": "Poll Interval (seconds)", - "settings_poll_interval_hint": "How often the bridge queries printer status", - "fd_options_title": "Print Options", - "print_auto_leveling": "Auto-Leveling", - "settings_file_ready_mode": "Start Print Behavior", - "settings_file_ready_banner": "Print Bar", - "settings_file_ready_dialog": "Print Dialog", - "settings_camera_on_print": "Turn camera on at print start", - "settings_web_upload_warning": "Show warning when printing web uploads", - "settings_filament_mapping_label": "Filament profile mapping (per slot)", - "settings_filament_mapping_hint": "Fixed Orca profile per AMS slot. On slicer sync, the bridge sends this profile instead of \"Generic\".", - "settings_filament_mapping_save_label": "Save mapping", - "settings_visible_vendors_label": "Visible vendors (profile dropdown)", - "settings_visible_vendors_save_label": "Save selection", - "settings_vendor_filter_placeholder": "Search vendors…", - "update_check": "Check for Updates", - "update_checking": "Checking...", - "update_available": "available", - "update_none": "Already up to date", - "update_apply": "Install Now", - "update_applying": "Downloading...", - "update_restarting": "Restarting...", - "update_error": "Error", - "btn_connect": "⚡ Connect", - "btn_disconnect": "✕ Disconnect", - "lbl_conn_error": "Connection error:", - "slot_edit_title": "Edit Slot", - "slot_edit_color": "Color", - "slot_edit_material": "Material", - "slot_edit_load": "⬇ Load", - "slot_edit_unload": "⬆ Unload", - "slot_edit_save": "💾 Save", - "slot_edit_custom": "e.g. PLA, PETG, ABS…", - "slot_edit_ok": "AMS Slot", - "slot_edit_profile": "OrcaSlicer profile", - "slot_edit_profile_hint": "Sent on OrcaSlicer sync as the specific brand instead of just \"Generic\"", - "slot_edit_profile_default": "— Generic (default) —", - "orca_profile_section": "OrcaSlicer Profiles", + "nav_temps": "Temperatures", + "orca_profile_done": "Imported", + "orca_profile_dropmsg": "Drop here or click", + "orca_profile_help_html": "Upload a ZIP of your OrcaSlicer filament folder or single .json files.
In OrcaSlicer: Help → Show Configuration Folder → user/<id>/filament/", "orca_profile_hint": "Import your own OrcaSlicer filament profiles (open the user dir via Help → Show Configuration Folder)", "orca_profile_import_btn": "Import profiles", "orca_profile_import_link": "★ Import own profiles…", "orca_profile_import_title": "Import your OrcaSlicer profiles", - "orca_profile_help_html": "Upload a ZIP of your OrcaSlicer filament folder or single .json files.
In OrcaSlicer: Help → Show Configuration Folder → user/<id>/filament/", - "orca_profile_dropmsg": "Drop here or click", "orca_profile_list_label": "Currently imported", - "orca_profile_user_label": "Own profiles", - "orca_profile_user_empty": "– none –", - "orca_profile_uploading": "Uploading…", - "orca_profile_done": "Imported", + "orca_profile_section": "OrcaSlicer Profiles", "orca_profile_skipped": "skipped", - "log_dir_all": "All", - "log_dir_rx": "RX", - "log_dir_tx": "TX", - "log_dir_label": "Dir:", - "log_lvl_label": "Level:", - "log_lvl_err": "⛔ Errors", - "log_lvl_warn": "⚠ Warn", - "log_topic_label": "Topic:", - "log_topic_ams": "AMS", - "log_topic_print": "Print", - "log_topic_info": "Info", - "log_topic_status": "Status", - "log_download": "⬇ Download", - "log_auto": "⬇ Auto", - "log_clear": "✕ Clear", - "log_filter_placeholder": "Filter…", - "file_ready_btn": "▶ Start Print", - "file_slots_btn": "🎨 Select Slots", - "file_cancel_btn": "✕ Cancel", - "nav_printers": "Printers", - "skip_title": "✂ Skip objects", - "skip_hint": "Uncheck objects you no longer want to print:", - "skip_btn_label": "Objects", - "skip_no_objects": "No objects in this print.", + "orca_profile_uploading": "Uploading…", + "orca_profile_user_empty": "– none –", + "orca_profile_user_label": "Own profiles", + "panel_ams_title": "Filament", + "panel_browser_title": "File Browser", + "panel_console_title": "Event Log", + "panel_extras_camera": "Camera", + "panel_extras_fan": "Fan", + "panel_extras_light": "Light", + "panel_motion_xy": "XY Axes", + "panel_motion_z": "Z Axis", + "panel_print_btn_cancel": "✕ Cancel", + "panel_print_btn_pause": "⏸ Pause", + "panel_print_btn_resume": "▶ Resume", + "panel_print_temps_live": "Temperatures (Live)", + "panel_print_title": "Print Control", + "panel_temps_bed": "Heated Bed", + "panel_temps_chart": "History (last 60 readings)", + "panel_temps_nozzle": "Nozzle", + "print_auto_leveling": "Auto-Leveling", + "printers_active": "● active", + "printers_current": "Current printer", + "printers_empty_hint": "No printer set up yet.", + "printers_loading": "Loading…", + "printers_none": "No printers configured.", + "printers_remove": "Remove printer", + "printers_remove_confirm": "Remove printer \"{name}\"? The bridge will restart.", + "printers_switch": "Switch →", + "progress_action_clear": "Clear", + "progress_action_print": "Print", + "progress_action_slots": "Map slots", + "settings_auto_leveling": "Auto-Leveling Default", + "settings_auto_leveling_label": "Auto-Leveling before print", + "settings_btn_tooltip": "Settings", + "settings_camera_on_print": "Turn camera on at print start", + "settings_cat_connection": "Connection", + "settings_cat_display": "Appearance", + "settings_cat_filament": "Filament", + "settings_cat_notifications": "Notifications", + "settings_cat_printer": "Printer", + "settings_cat_system": "System", + "settings_connection": "Connection", + "settings_default_slot": "Default Slot (single color)", + "settings_device_id": "Device ID", + "settings_device_id_hint": "32 hex characters", + "settings_device_id_placeholder": "32 hex characters", + "settings_filament_mapping": "Filament profile mapping (per slot)", + "settings_filament_mapping_hint": "Fixed Orca profile per AMS slot. On slicer sync, the bridge sends this profile instead of \"Generic\".", + "settings_filament_mapping_label": "Filament profile mapping (per slot)", + "settings_filament_mapping_save": "Save mapping", + "settings_filament_mapping_save_label": "Save mapping", + "settings_file_ready_banner": "Print bar", + "settings_file_ready_dialog": "Print dialog", + "settings_file_ready_mode": "After upload: Start print behavior", + "settings_language": "Language", + "settings_mode_id": "Mode ID", + "settings_mode_id_placeholder": "20030", + "settings_mqtt_port": "MQTT Port", + "settings_mqtt_username_placeholder": "userXXXXXXXX", + "settings_notif_add": "Add notification", + "settings_notif_empty": "No notifications configured.", + "settings_notif_ev_cancelled": "Cancelled", + "settings_notif_ev_failed": "Failed", + "settings_notif_ev_finished": "Finished", + "settings_notif_ev_paused": "Paused", + "settings_notif_ev_progress": "Progress", + "settings_notif_ev_started": "Started", + "settings_notif_interval_lbl": "Repeat interval", + "settings_notif_layers_unit": "layers", + "settings_notif_min_unit": "min", + "settings_notif_send_image": "Image", + "settings_notif_test": "Test", + "settings_notif_test_fail": "Failed", + "settings_notif_test_ok": "Sent", + "settings_notif_zero_off": "(0 = off)", + "settings_notifications": "Notifications", + "settings_notifications_hint": "Send notifications via Apprise URLs (discord://, telegram://, gotify://, slack://, …)", + "settings_orca_profiles_import": "Import profiles", + "settings_orca_profiles_label": "OrcaSlicer Profiles", + "settings_password": "MQTT Password", + "settings_poll": "Poll Interval (seconds)", + "settings_poll_interval_hint": "How often the bridge queries printer status", + "settings_poll_interval_label": "Poll Interval (seconds)", + "settings_print": "Print Settings", + "settings_printer_ip": "Printer IP", + "settings_printer_name": "Printer Name", + "settings_printer_name_placeholder": "e.g. Kobra X Left", + "settings_save": "Save & Restart", + "settings_slot_auto": "Auto (all loaded slots)", + "settings_theme_toggle": "Toggle light / dark", + "settings_title": "Settings", + "settings_username": "MQTT Username", + "settings_vendor_filter_placeholder": "Search vendors…", + "settings_version": "Version", + "settings_visible_vendors": "Visible vendors (profile dropdown)", + "settings_visible_vendors_hint": "Only these vendors appear in the slot profile dropdown. Nothing selected = show all. \"Generic\" and your own profiles are always visible.", + "settings_visible_vendors_label": "Visible vendors (profile dropdown)", + "settings_visible_vendors_save": "Save selection", + "settings_visible_vendors_save_label": "Save selection", + "settings_web_upload_warning": "Show warning when printing web uploads", + "sf_all": "All", + "sf_err": "✗ Failed", + "sf_new": "New", + "sf_ok": "✓ Completed", "skip_already": "skipped", + "skip_btn_label": "Objects", "skip_cancel": "Cancel", "skip_confirm": "Skip", + "skip_confirm_btn": "Skip", + "skip_hint": "Uncheck objects you no longer want to print:", + "skip_no_objects": "No objects in this print.", "skip_select_at_least_one": "Please pick at least one object.", "skip_sending": "Sending …", "skip_success": "Objects will be skipped.", - "fd_objects_hint": "Skip objects (optional):", - "fd_objects_toggle": "Skip objects", - "fd_slots_hint": "Assign GCode channel to AMS slot:", - "fd_cancel": "Cancel", - "fd_print": "▶ Print", - "fd_no_slots_msg": "No loaded AMS slots.{br}Start print anyway?", - "fd_slot": "Slot", - "fd_no_matching_material": "No matching material", - "fd_used": "USED", - "add_printer": "Add printer", - "apd_lbl_ip": "Printer IP", - "apd_lbl_name": "Name (optional)", - "apd_placeholder_name": "e.g. Kobra X Living Room", - "apd_cancel": "Cancel", - "apd_confirm": "Add", - "apd_fetching": "Fetching data from printer…", - "apd_success": "Printer added, bridge restarting…", - "apd_err_ip": "Please enter an IP address", - "printers_remove": "Remove printer", - "printers_remove_confirm": "Remove printer \"{name}\"? The bridge will restart.", - "printers_active": "● active", - "printers_switch": "Switch →", - "printers_current": "Current printer", - "printers_loading": "Loading…", - "printers_none": "No printers configured.", - "printers_empty_hint": "No printer set up yet.", - "nav_browser": "Browser", - "panel_browser_title": "File Browser", - "store_search_placeholder": "🔍 Search…", - "store_empty": "No files uploaded yet.", - "store_refresh": "↻ Refresh", - "store_print": "▶ Print", - "store_download": "⬇ Download", - "store_delete_confirm": "Delete file?", - "store_print_confirm": "Print file?", - "store_web_verify_title": "Verify file", - "store_web_verify_msg": "Please verify this file was made for Anycubic Kobra X.", - "store_web_verify_confirm": "Confirm", - "store_web_verify_abort": "Abort", - "store_no_results": "No files found.", - "store_never": "never printed", - "store_estimate": "Estimate", - "store_upload_label_prefix": "Drag GCode here or ", - "store_upload_label_browse": "browse", - "store_upload_busy": "⏳ Uploading…", - "store_upload_success": "✓ {file}", - "store_upload_error": "✗ {error}", - "store_upload_only_gcode": "✗ Only GCode files allowed (.gcode, .3mf, .bgcode)", - "sf_all": "All", - "sf_ok": "✓ Completed", - "sf_err": "✗ Failed", - "sf_new": "New", + "skip_title": "✂ Skip objects", + "slot_edit_color": "Color", + "slot_edit_custom": "e.g. PLA, PETG, ABS…", + "slot_edit_load": "⬇ Load", + "slot_edit_material": "Material", + "slot_edit_ok": "AMS Slot", + "slot_edit_profile": "OrcaSlicer profile", + "slot_edit_profile_default": "— Generic (default) —", + "slot_edit_profile_hint": "Sent on OrcaSlicer sync as the specific brand instead of just \"Generic\"", + "slot_edit_save": "💾 Save", + "slot_edit_title": "Edit Slot", + "slot_edit_unload": "⬆ Unload", + "speed_normal": "⚡ Normal", + "speed_silent": "🐢 Silent", + "speed_sport": "🚀 Sport", "ss_date": "↓ Date", - "ss_name": "A–Z Name", "ss_dur": "⏱ Print time", - "settings_notifications": "Notifications", - "settings_notifications_hint": "Send notifications via Apprise URLs (discord://, telegram://, gotify://, slack://, …)", - "settings_notif_add": "Add notification", - "settings_notif_empty": "No notifications configured.", - "settings_notif_test": "Test", - "settings_notif_test_ok": "Sent", - "settings_notif_test_fail": "Failed", - "settings_notif_ev_finished": "Finished", - "settings_notif_ev_failed": "Failed", - "settings_notif_ev_cancelled": "Cancelled", - "settings_notif_ev_paused": "Paused", - "settings_notif_ev_started": "Started", - "settings_notif_ev_progress": "Progress", - "settings_notif_interval_lbl": "Repeat interval", - "settings_notif_min_unit": "min", - "settings_notif_layers_unit": "layers", - "settings_notif_zero_off": "(0 = off)", - "settings_notif_send_image": "Image", - "settings_btn_tooltip": "Settings", - "settings_printer_name_placeholder": "e.g. Kobra X Left", - "settings_device_id_placeholder": "32 hex characters", - "settings_mqtt_username_placeholder": "userXXXXXXXX", - "settings_device_id_hint": "32 hex characters", - "settings_mode_id_placeholder": "20030", - "settings_language": "Language", - "settings_theme_toggle": "Toggle light / dark", - "settings_orca_profiles_label": "OrcaSlicer Profiles", - "settings_orca_profiles_import": "Import profiles", - "skip_confirm_btn": "Skip", - "btn_delete": "Delete", - "log_print_start": "Print start:", - "log_print_action": "Print:", - "log_delete_failed": "Delete failed" + "ss_name": "A–Z Name", + "store_delete_confirm": "Delete file?", + "store_download": "⬇ Download", + "store_empty": "No files uploaded yet.", + "store_estimate": "Estimate", + "store_never": "never printed", + "store_no_results": "No files found.", + "store_print": "▶ Print", + "store_print_confirm": "Print file?", + "store_refresh": "↻ Refresh", + "store_search_placeholder": "🔍 Search…", + "store_upload_busy": "⏳ Uploading…", + "store_upload_error": "✗ {error}", + "store_upload_label_browse": "browse", + "store_upload_label_prefix": "Drag GCode here or ", + "store_upload_only_gcode": "✗ Only GCode files allowed (.gcode, .3mf, .bgcode)", + "store_upload_success": "✓ {file}", + "store_web_verify_abort": "Cancel", + "store_web_verify_confirm": "Confirm", + "store_web_verify_msg": "Please verify this file was made for Anycubic Kobra X.", + "store_web_verify_title": "Verify file", + "update_apply": "Install Now", + "update_applying": "Downloading...", + "update_available": "available", + "update_check": "Check for Updates", + "update_checking": "Checking...", + "update_error": "Error", + "update_none": "Already up to date", + "update_restarting": "Restarting..." } diff --git a/web/translations/es.json b/web/translations/es.json index 7024055..7f4e877 100644 --- a/web/translations/es.json +++ b/web/translations/es.json @@ -1,328 +1,331 @@ { - "header_status_standby": "Listo", - "header_status_printing": "Imprimiendo", - "header_status_complete": "Completado", - "header_status_error": "Error", - "kobra_free": "Listo", - "kobra_busy": "Ocupado", - "kobra_printing": "Imprimiendo", - "kobra_preheating": "Precalentando", - "kobra_auto_leveling": "Autonivelado", - "kobra_checking": "Comprobando", - "kobra_updated": "Actualizando", - "kobra_init": "Inicializando", - "kobra_pausing": "Pausando...", - "kobra_paused": "Pausado", - "kobra_resuming": "Reanudando...", - "kobra_resumed": "Reanudado", - "kobra_stopping": "Deteniendo...", - "kobra_stoped": "Detenido", - "kobra_finished": "Finalizado", - "kobra_failed": "Error", - "kobra_canceled": "Cancelado", - "kobra_offline": "Desconectada", - "nav_dashboard": "Panel", - "nav_print": "Impresión", - "nav_temps": "Temperaturas", - "nav_motion": "Movimiento", - "nav_ams": "AMS", - "nav_extras": "Luz / Ventilador", - "nav_console": "Consola", - "card_progress": "Progreso", - "card_temps": "Temperaturas", - "card_light_fan": "Ventilador", - "card_speed": "Velocidad de impresión", - "card_cam": "Cámara", - "lbl_elapsed": "Transcurrido:", - "lbl_remaining": "Restante:", - "lbl_slicer_time": "Estimación del slicer:", - "lbl_layers": "Capa", - "lbl_zpos": "Z (mm)", - "speed_silent": "🐢 Silencioso", - "speed_normal": "⚡ Normal", - "speed_sport": "🚀 Sport", - "lbl_light": "💡 Luz", - "lbl_feed": "Cargar", - "lbl_unload": "Descargar", - "card_ace_dry": "Secado ACE", + "ace_dry_auto_refill": "Relleno automático", + "ace_dry_chart": "Historial (Temp/Humedad)", + "ace_dry_current_temp": "Temperatura", + "ace_dry_dialog_cancel": "Cancelar", + "ace_dry_dialog_confirm": "Confirmar", + "ace_dry_dialog_custom_name": "Nombre personalizado", + "ace_dry_dialog_reset_default": "Restablecer valores predeterminados", + "ace_dry_dialog_save_restart": "Guardar y reiniciar", + "ace_dry_dialog_temp": "Temperatura (30-80°C)", + "ace_dry_dialog_time": "Tiempo restante (h:m:s)", + "ace_dry_dialog_title": "Ajustes de temp/tiempo del secador", "ace_dry_dryer": "Secador", + "ace_dry_duration": "Duración (min)", + "ace_dry_enable": "Activar secado", + "ace_dry_humidity": "Humedad", + "ace_dry_preset_abs_asa": "ABS / ASA", + "ace_dry_preset_custom": "Personalizado", + "ace_dry_preset_pa_pc": "PA / PC", + "ace_dry_preset_petg": "PETG", + "ace_dry_preset_pla": "PLA", + "ace_dry_preset_pla_plus": "PLA+", + "ace_dry_preset_tpu": "TPU", + "ace_dry_start": "▶ Iniciar", "ace_dry_status_off": "Estado: Apagado", "ace_dry_status_on": "Estado: Activo", "ace_dry_status_remaining": "Restante", - "ace_dry_humidity": "Humedad", - "ace_dry_current_temp": "Temperatura", - "ace_dry_chart": "Historial (Temp/Humedad)", - "ace_dry_temp": "Temperatura (°C)", - "ace_dry_duration": "Duración (min)", - "ace_dry_start": "▶ Iniciar", "ace_dry_stop": "■ Parar", - "ace_dry_auto_refill": "Relleno automático", - "ace_dry_enable": "Activar secado", + "ace_dry_temp": "Temperatura (°C)", "ace_dry_temp_line": "Temperatura de secado", "ace_dry_time_line": "Tiempo de secado", "ace_dry_ui_pending": "(solo UI, backend después)", - "ace_dry_dialog_title": "Ajustes de temp/tiempo del secador", - "ace_dry_dialog_temp": "Temperatura (30-80°C)", - "ace_dry_dialog_time": "Tiempo restante (h:m:s)", - "ace_dry_dialog_confirm": "Confirmar", - "ace_dry_dialog_cancel": "Cancelar", - "ace_dry_dialog_save_restart": "Guardar y reiniciar", - "ace_dry_dialog_custom_name": "Nombre personalizado", - "ace_dry_dialog_reset_default": "Restablecer valores predeterminados", - "cam_placeholder": "📷 Cámara no iniciada", - "cam_stream_unavailable": "Stream no disponible", + "add_printer": "Añadir impresora", + "ams_empty": "Vacío", + "ams_no_data": "No se recibieron datos de AMS", + "apd_cancel": "Cancelar", + "apd_confirm": "Agregar", + "apd_err_ip": "Introduce una dirección IP", + "apd_fetching": "Obteniendo datos de la impresora…", + "apd_lbl_ip": "IP de impresora", + "apd_lbl_name": "Nombre (opcional)", + "apd_placeholder_name": "p. ej. Kobra X Sala", + "apd_success": "Impresora añadida, reiniciando bridge…", + "apd_title": "Agregar impresora", "btn_cam_start": "▶ Cámara", + "btn_cam_start2": "▶ Iniciar", "btn_cam_stop": "◼ Cámara", + "btn_cam_stop2": "◼ Detener", + "btn_cancel": "✕ Detener", + "btn_cancel_generic": "Cancelar", + "btn_confirm_generic": "Confirmar", + "btn_connect": "⚡ Conectar", + "btn_delete": "Eliminar", + "btn_disable_motors": "Motores apagados", + "btn_disconnect": "✕ Desconectar", + "btn_home_all": "Home All", + "btn_home_xy": "Home XY", + "btn_home_z": "Home Z", "btn_pause": "⏸ Pausa", "btn_resume": "▶ Reanudar", - "btn_cancel": "✕ Detener", - "label_nozzle": "Boquilla", + "cam_placeholder": "📷 Cámara no iniciada", + "cam_stream_unavailable": "Stream no disponible", + "card_ace_dry": "Secado ACE", + "card_ams": "Filamento", + "card_cam": "Cámara", + "card_light_fan": "Ventilador", + "card_progress": "Progreso", + "card_speed": "Velocidad de impresión", + "card_temps": "Temperaturas", + "confirm_cancel": "¿Realmente cancelar la impresión?", + "fd_cancel": "Cancelar", + "fd_no_matching_material": "No hay material compatible", + "fd_no_slots_msg": "No hay slots AMS cargados.{br}¿Iniciar impresión de todos modos?", + "fd_objects_hint": "Omitir objetos (opcional):", + "fd_objects_toggle": "Omitir objetos", + "fd_options_title": "Opciones", + "fd_print": "▶ Imprimir", + "fd_slot": "Ranura", + "fd_slots_hint": "Asignar canal GCode a la ranura AMS:", + "fd_used": "USADO", + "file_cancel_btn": "✕ Cancelar", + "file_ready_btn": "▶ Iniciar impresión", + "file_slots_btn": "🎨 Seleccionar ranuras", + "header_status_complete": "Completado", + "header_status_error": "Error", + "header_status_printing": "Imprimiendo", + "header_status_standby": "Listo", + "hint_ip_no_port": "Solo dirección IP, sin puerto (p. ej. 192.168.1.102)", + "kobra_auto_leveling": "Autonivelado", + "kobra_busy": "Ocupado", + "kobra_canceled": "Cancelado", + "kobra_checking": "Comprobando", + "kobra_failed": "Error", + "kobra_finished": "Finalizado", + "kobra_free": "Listo", + "kobra_init": "Inicializando", + "kobra_offline": "Desconectada", + "kobra_paused": "Pausado", + "kobra_pausing": "Pausando...", + "kobra_preheating": "Precalentando", + "kobra_printing": "Imprimiendo", + "kobra_resumed": "Reanudado", + "kobra_resuming": "Reanudando...", + "kobra_stoped": "Detenido", + "kobra_stopping": "Deteniendo...", + "kobra_updated": "Actualizando", "label_bed": "Cama", "label_fan": "🌀 Ventilador", "label_light": "💡 Luz", - "label_on_off": "Encendido / Apagado", - "label_speed": "Velocidad", - "panel_print_title": "Control de impresión", - "panel_print_btn_pause": "⏸ Pausa", - "panel_print_btn_resume": "▶ Reanudar", - "panel_print_btn_cancel": "✕ Cancelar", - "panel_print_temps_live": "Temperaturas (en vivo)", - "label_set": "Set", + "label_nozzle": "Boquilla", "label_off": "Apagado", - "panel_temps_nozzle": "Boquilla", - "panel_temps_bed": "Cama caliente", - "panel_temps_chart": "Historial (últimas 60 lecturas)", - "label_target_c": "Objetivo:", - "panel_motion_xy": "Ejes XY", - "panel_motion_z": "Eje Z", - "label_step": "Tamaño del paso:", - "btn_home_z": "Home Z", - "btn_home_xy": "Home XY", - "btn_home_all": "Home All", - "btn_disable_motors": "Motores apagados", - "panel_ams_title": "Filamento", - "card_ams": "Filamento", - "ams_no_data": "No se recibieron datos de AMS", + "label_on_off": "Encendido / Apagado", + "label_set": "Set", "label_slot": "Ranura", - "ams_empty": "Vacío", - "panel_extras_light": "Luz", - "panel_extras_fan": "Ventilador", - "panel_extras_camera": "Cámara", - "btn_cam_start2": "▶ Iniciar", - "btn_cam_stop2": "◼ Detener", - "panel_console_title": "Registro de eventos", - "log_light_on": "Luz encendida", - "log_light_off": "Luz apagada", - "log_fan": "Ventilador →", - "log_nozzle": "Boquilla →", - "log_bed": "Cama →", + "label_speed": "Velocidad", + "label_step": "Tamaño del paso:", + "label_target_c": "Objetivo:", + "lbl_conn_error": "Error de conexión:", + "lbl_elapsed": "Transcurrido:", + "lbl_feed": "Cargar", + "lbl_layers": "Capa", + "lbl_light": "💡 Luz", + "lbl_remaining": "Restante:", + "lbl_slicer_time": "Estimación del slicer:", + "lbl_unload": "Descargar", + "lbl_zpos": "Z (mm)", + "log_auto": "⬇ Auto", "log_axis": "Eje", - "log_home": "Home", - "log_home_all": "Home All", + "log_bed": "Cama →", "log_cam_start": "Cámara iniciada:", "log_cam_stop": "Cámara detenida", - "log_poll_error": "Error de sondeo:", + "log_clear": "✕ Limpiar", + "log_delete_failed": "Error al eliminar", + "log_dir_all": "Todos", + "log_dir_label": "Dirección:", + "log_dir_rx": "RX", + "log_dir_tx": "TX", + "log_download": "⬇ Descargar", "log_error": "Error:", - "confirm_cancel": "¿Realmente cancelar la impresión?", - "settings_title": "Configuración", - "settings_connection": "Conexión", - "settings_print": "Ajustes de impresión", - "settings_poll": "Intervalo de sondeo (segundos)", + "log_fan": "Ventilador →", + "log_filter_placeholder": "Filtrar…", + "log_home": "Home", + "log_home_all": "Home All", + "log_light_off": "Luz apagada", + "log_light_on": "Luz encendida", + "log_lvl_err": "⛔ Errores", + "log_lvl_label": "Nivel:", + "log_lvl_warn": "⚠ Avisos", + "log_nozzle": "Boquilla →", + "log_poll_error": "Error de sondeo:", + "log_print_action": "Impresión:", + "log_print_start": "Inicio de impresión:", + "log_topic_ams": "AMS", + "log_topic_info": "Info", + "log_topic_label": "Tema:", + "log_topic_print": "Impresión", + "log_topic_status": "Estado", + "nav_ams": "AMS", + "nav_browser": "Explorador", + "nav_console": "Consola", + "nav_dashboard": "Panel", + "nav_extras": "Luz / Ventilador", + "nav_motion": "Movimiento", + "nav_print": "Impresión", + "nav_printers": "Impresoras", "nav_settings": "Ajustes", - "settings_cat_display": "Apariencia", - "settings_cat_filament": "Filamento", - "settings_cat_language": "Idioma", - "settings_cat_theme": "Alternar claro / oscuro", - "settings_filament_mapping": "Asignación de perfil de filamento (por ranura)", - "settings_filament_mapping_save": "Guardar asignación", - "settings_visible_vendors": "Fabricantes visibles (lista de perfiles)", - "settings_visible_vendors_hint": "Solo estos fabricantes aparecen en la lista de perfiles de ranura. Nada seleccionado = mostrar todos. «Generic» y tus propios perfiles siempre son visibles.", - "settings_visible_vendors_save": "Guardar selección", - "progress_action_print": "Imprimir", - "progress_action_slots": "Asignar ranuras", - "progress_action_clear": "Vaciar", - "settings_version": "Versión", - "settings_save": "Guardar y reiniciar", - "settings_printer_name": "Nombre de impresora", - "settings_printer_ip": "IP de impresora", - "settings_mqtt_port": "MQTT Port", - "settings_username": "Usuario MQTT", - "settings_password": "Contraseña MQTT", - "settings_device_id": "ID del dispositivo", - "settings_mode_id": "ID de modo", - "hint_ip_no_port": "Solo dirección IP, sin puerto (p. ej. 192.168.1.102)", - "settings_default_slot": "Ranura predeterminada (un color)", - "settings_slot_auto": "Auto (todos los slots cargados)", - "settings_auto_leveling": "Autonivelado antes de imprimir", - "settings_camera_on_print": "Encender cámara al iniciar impresión", - "settings_web_upload_warning": "Mostrar advertencia al imprimir subidas web", - "update_check": "Buscar actualizaciones", - "update_checking": "Comprobando...", - "update_available": "disponible", - "update_none": "Ya actualizado", - "update_apply": "Instalar ahora", - "update_applying": "Descargando...", - "update_restarting": "Reiniciando...", - "update_error": "Error", - "btn_connect": "⚡ Conectar", - "btn_disconnect": "✕ Desconectar", - "lbl_conn_error": "Error de conexión:", - "slot_edit_title": "Editar slot", - "slot_edit_color": "Color", - "slot_edit_material": "Material", - "slot_edit_load": "⬇ Cargar", - "slot_edit_unload": "⬆ Descargar", - "slot_edit_save": "💾 Guardar", - "slot_edit_custom": "p. ej. PLA, PETG, ABS…", - "slot_edit_ok": "Ranura AMS", - "slot_edit_profile": "Perfil de OrcaSlicer", - "slot_edit_profile_hint": "Envía al sincronizar con OrcaSlicer la marca concreta en lugar de solo \"Generic\"", - "slot_edit_profile_default": "— Genérico (Predeterminado) —", - "orca_profile_section": "Perfiles de OrcaSlicer", + "nav_temps": "Temperaturas", + "orca_profile_done": "Importado", + "orca_profile_dropmsg": "Suelta aquí o haz clic", + "orca_profile_help_html": "Sube un ZIP de tu carpeta de filamentos de OrcaSlicer o archivos .json sueltos.
En OrcaSlicer: Help → Show Configuration Folder → user/<id>/filament/", "orca_profile_hint": "Importa tus propios perfiles de filamento de OrcaSlicer (abre el directorio del usuario vía Help → Show Configuration Folder)", "orca_profile_import_btn": "Importar perfiles", "orca_profile_import_link": "★ Importar perfiles propios…", "orca_profile_import_title": "Importar tus perfiles de OrcaSlicer", - "orca_profile_help_html": "Sube un ZIP de tu carpeta de filamentos de OrcaSlicer o archivos .json sueltos.
En OrcaSlicer: Help → Show Configuration Folder → user/<id>/filament/", - "orca_profile_dropmsg": "Suelta aquí o haz clic", "orca_profile_list_label": "Actualmente importados", - "orca_profile_user_label": "Perfiles propios", - "orca_profile_user_empty": "– ninguno –", - "orca_profile_uploading": "Subiendo…", - "orca_profile_done": "Importado", + "orca_profile_section": "Perfiles de OrcaSlicer", "orca_profile_skipped": "omitido", - "log_dir_all": "Todos", - "log_lvl_label": "Nivel:", - "file_ready_btn": "▶ Iniciar impresión", - "file_slots_btn": "🎨 Seleccionar ranuras", - "file_cancel_btn": "✕ Cancelar", - "nav_printers": "Impresoras", - "skip_title": "✂ Omitir objetos", - "skip_hint": "Deselecciona los objetos que ya no quieras imprimir:", - "skip_btn_label": "Objetos", - "skip_no_objects": "No hay objetos en esta impresión.", + "orca_profile_uploading": "Subiendo…", + "orca_profile_user_empty": "– ninguno –", + "orca_profile_user_label": "Perfiles propios", + "panel_ams_title": "Filamento", + "panel_browser_title": "Explorador de archivos", + "panel_console_title": "Registro de eventos", + "panel_extras_camera": "Cámara", + "panel_extras_fan": "Ventilador", + "panel_extras_light": "Luz", + "panel_motion_xy": "Ejes XY", + "panel_motion_z": "Eje Z", + "panel_print_btn_cancel": "✕ Cancelar", + "panel_print_btn_pause": "⏸ Pausa", + "panel_print_btn_resume": "▶ Reanudar", + "panel_print_temps_live": "Temperaturas (en vivo)", + "panel_print_title": "Control de impresión", + "panel_temps_bed": "Cama caliente", + "panel_temps_chart": "Historial (últimas 60 lecturas)", + "panel_temps_nozzle": "Boquilla", + "print_auto_leveling": "Autonivelado para esta impresión", + "printers_active": "● activa", + "printers_current": "Impresora actual", + "printers_empty_hint": "Aún no hay impresora configurada.", + "printers_loading": "Cargando…", + "printers_none": "No hay impresoras configuradas.", + "printers_remove": "Eliminar impresora", + "printers_remove_confirm": "¿Eliminar impresora \"{name}\"? El bridge se reiniciará.", + "printers_switch": "Cambiar →", + "progress_action_clear": "Vaciar", + "progress_action_print": "Imprimir", + "progress_action_slots": "Asignar ranuras", + "settings_auto_leveling": "Autonivelado antes de imprimir", + "settings_btn_tooltip": "Ajustes", + "settings_camera_on_print": "Encender cámara al iniciar impresión", + "settings_cat_connection": "Conexión", + "settings_cat_display": "Apariencia", + "settings_cat_filament": "Filamento", + "settings_cat_language": "Idioma", + "settings_cat_notifications": "Notificaciones", + "settings_cat_printer": "Impresora", + "settings_cat_system": "Sistema", + "settings_cat_theme": "Alternar claro / oscuro", + "settings_connection": "Conexión", + "settings_default_slot": "Ranura predeterminada (un color)", + "settings_device_id": "ID del dispositivo", + "settings_device_id_hint": "32 caracteres hexadecimales", + "settings_device_id_placeholder": "32 caracteres hexadecimales", + "settings_filament_mapping": "Asignación de perfil de filamento (por ranura)", + "settings_filament_mapping_save": "Guardar asignación", + "settings_file_ready_banner": "Barra de impresión", + "settings_file_ready_dialog": "Diálogo de impresión", + "settings_file_ready_mode": "Después de carga: Comportamiento de inicio de impresión", + "settings_language": "Idioma", + "settings_mode_id": "ID de modo", + "settings_mode_id_placeholder": "20030", + "settings_mqtt_port": "MQTT Port", + "settings_mqtt_username_placeholder": "userXXXXXXXX", + "settings_notif_add": "Agregar notificación", + "settings_notif_empty": "Sin notificaciones configuradas.", + "settings_notif_ev_cancelled": "Cancelado", + "settings_notif_ev_failed": "Error", + "settings_notif_ev_finished": "Finalizado", + "settings_notif_ev_paused": "Pausado", + "settings_notif_ev_progress": "Progreso", + "settings_notif_ev_started": "Iniciado", + "settings_notif_interval_lbl": "Intervalo de repetición", + "settings_notif_layers_unit": "capas", + "settings_notif_min_unit": "min", + "settings_notif_send_image": "Imagen", + "settings_notif_test": "Prueba", + "settings_notif_test_fail": "Error", + "settings_notif_test_ok": "Enviado", + "settings_notif_zero_off": "(0 = desactivado)", + "settings_notifications": "Notificaciones", + "settings_notifications_hint": "Envía notificaciones a través de URLs de Apprise (discord://, telegram://, gotify://, slack://, …)", + "settings_orca_profiles_import": "Importar perfiles", + "settings_orca_profiles_label": "Perfiles de OrcaSlicer", + "settings_password": "Contraseña MQTT", + "settings_poll": "Intervalo de sondeo (segundos)", + "settings_print": "Ajustes de impresión", + "settings_printer_ip": "IP de impresora", + "settings_printer_name": "Nombre de impresora", + "settings_printer_name_placeholder": "p. ej. Kobra X Sala", + "settings_save": "Guardar y reiniciar", + "settings_slot_auto": "Auto (todos los slots cargados)", + "settings_theme_toggle": "Alternar claro / oscuro", + "settings_title": "Configuración", + "settings_username": "Usuario MQTT", + "settings_version": "Versión", + "settings_visible_vendors": "Fabricantes visibles (lista de perfiles)", + "settings_visible_vendors_hint": "Solo estos fabricantes aparecen en la lista de perfiles de ranura. Nada seleccionado = mostrar todos. «Generic» y tus propios perfiles siempre son visibles.", + "settings_visible_vendors_save": "Guardar selección", + "settings_web_upload_warning": "Mostrar advertencia al imprimir subidas web", + "sf_all": "Todos", + "sf_err": "✗ Fallido", + "sf_new": "Nuevo", + "sf_ok": "✓ Completado", "skip_already": "omitido", + "skip_btn_label": "Objetos", + "skip_cancel": "Cancelar", + "skip_confirm": "Omitir", + "skip_confirm_btn": "Omitir", + "skip_hint": "Deselecciona los objetos que ya no quieras imprimir:", + "skip_no_objects": "No hay objetos en esta impresión.", "skip_select_at_least_one": "Selecciona al menos un objeto.", "skip_sending": "Enviando …", "skip_success": "Se omitirán los objetos.", - "fd_objects_hint": "Omitir objetos (opcional):", - "fd_objects_toggle": "Omitir objetos", - "fd_slots_hint": "Asignar canal GCode a la ranura AMS:", - "fd_cancel": "Cancelar", - "fd_print": "▶ Imprimir", - "fd_no_slots_msg": "No hay slots AMS cargados.{br}¿Iniciar impresión de todos modos?", - "fd_slot": "Ranura", - "fd_no_matching_material": "No hay material compatible", - "fd_used": "USADO", - "add_printer": "Añadir impresora", - "apd_lbl_ip": "IP de impresora", - "apd_lbl_name": "Nombre (opcional)", - "apd_placeholder_name": "p. ej. Kobra X Sala", - "apd_cancel": "Cancelar", - "apd_confirm": "Añadir", - "apd_fetching": "Obteniendo datos de la impresora…", - "apd_success": "Impresora añadida, reiniciando bridge…", - "apd_err_ip": "Introduce una dirección IP", - "printers_remove": "Eliminar impresora", - "printers_remove_confirm": "¿Eliminar impresora \"{name}\"? El bridge se reiniciará.", - "printers_active": "● activa", - "printers_switch": "Cambiar →", - "printers_current": "Impresora actual", - "printers_loading": "Cargando…", - "printers_none": "No hay impresoras configuradas.", - "printers_empty_hint": "Aún no hay impresora configurada.", - "nav_browser": "Explorador", - "panel_browser_title": "Explorador de archivos", - "store_search_placeholder": "🔍 Buscar…", - "store_empty": "Aún no hay archivos subidos.", - "store_refresh": "↻ Actualizar", - "store_print": "▶ Imprimir", - "store_download": "⬇ Descargar", - "store_delete_confirm": "¿Eliminar archivo?", - "store_print_confirm": "¿Imprimir archivo?", - "store_web_verify_title": "Verificar archivo", - "store_web_verify_msg": "Verifica que este archivo fue creado para Anycubic Kobra X.", - "store_web_verify_confirm": "Confirmar", - "store_web_verify_abort": "Abortar", - "store_no_results": "No se encontraron archivos.", - "store_never": "nunca impreso", - "store_estimate": "Estimación", - "store_upload_label_prefix": "Arrastra el GCode aquí o ", - "store_upload_label_browse": "buscar", - "store_upload_busy": "⏳ Subiendo…", - "store_upload_success": "✓ {file}", - "store_upload_error": "✗ {error}", - "store_upload_only_gcode": "✗ Solo se permiten archivos GCode (.gcode, .3mf, .bgcode)", - "sf_all": "Todos", - "sf_ok": "✓ Completado", - "sf_err": "✗ Fallido", - "sf_new": "Nuevo", + "skip_title": "✂ Omitir objetos", + "slot_edit_color": "Color", + "slot_edit_custom": "p. ej. PLA, PETG, ABS…", + "slot_edit_load": "⬇ Cargar", + "slot_edit_material": "Material", + "slot_edit_ok": "Ranura AMS", + "slot_edit_profile": "Perfil de OrcaSlicer", + "slot_edit_profile_default": "— Genérico (Predeterminado) —", + "slot_edit_profile_hint": "Envía al sincronizar con OrcaSlicer la marca concreta en lugar de solo \"Generic\"", + "slot_edit_save": "💾 Guardar", + "slot_edit_title": "Editar slot", + "slot_edit_unload": "⬆ Descargar", + "speed_normal": "⚡ Normal", + "speed_silent": "🐢 Silencioso", + "speed_sport": "🚀 Sport", "ss_date": "↓ Fecha", - "ss_name": "A–Z Nombre", "ss_dur": "⏱ Tiempo de impresión", - "ace_dry_preset_pla": "PLA", - "ace_dry_preset_pla_plus": "PLA+", - "ace_dry_preset_petg": "PETG", - "ace_dry_preset_tpu": "TPU", - "ace_dry_preset_abs_asa": "ABS / ASA", - "ace_dry_preset_pa_pc": "PA / PC", - "ace_dry_preset_custom": "Personalizado", - "fd_options_title": "Opciones", - "print_auto_leveling": "Autonivelado para esta impresión", - "settings_file_ready_mode": "Iniciar diálogo de impresión", - "settings_file_ready_banner": "Barra de impresión", - "settings_file_ready_dialog": "Diálogo de impresión", - "log_dir_rx": "RX", - "log_dir_tx": "TX", - "log_dir_label": "Dirección:", - "log_lvl_err": "⛔ Errores", - "log_lvl_warn": "⚠ Avisos", - "log_topic_label": "Tema:", - "log_topic_ams": "AMS", - "log_topic_print": "Impresión", - "log_topic_info": "Info", - "log_topic_status": "Estado", - "log_download": "⬇ Descargar", - "log_auto": "⬇ Auto", - "log_clear": "✕ Limpiar", - "log_filter_placeholder": "Filtrar…", - "skip_cancel": "Cancelar", - "skip_confirm": "Omitir", - "settings_cat_connection": "Conexión", - "settings_cat_printer": "Impresora", - "settings_cat_notifications": "Notificaciones", - "settings_cat_system": "Sistema", - "settings_notifications": "Notificaciones", - "settings_notifications_hint": "Envía notificaciones a través de URLs de Apprise (discord://, telegram://, gotify://, slack://, …)", - "settings_notif_add": "Agregar notificación", - "settings_notif_empty": "Sin notificaciones configuradas.", - "settings_notif_test": "Prueba", - "settings_notif_test_ok": "Enviado", - "settings_notif_test_fail": "Error", - "settings_notif_ev_finished": "Finalizado", - "settings_notif_ev_failed": "Error", - "settings_notif_ev_cancelled": "Cancelado", - "settings_notif_ev_paused": "Pausado", - "settings_notif_ev_started": "Iniciado", - "settings_notif_ev_progress": "Progreso", - "settings_notif_interval_lbl": "Intervalo de repetición", - "settings_notif_min_unit": "min", - "settings_notif_layers_unit": "capas", - "settings_notif_zero_off": "(0 = desactivado)", - "settings_notif_send_image": "Imagen", - "settings_btn_tooltip": "Ajustes", - "settings_printer_name_placeholder": "p. ej. Kobra X Sala", - "settings_device_id_placeholder": "32 caracteres hexadecimales", - "settings_mqtt_username_placeholder": "userXXXXXXXX", - "settings_device_id_hint": "32 caracteres hexadecimales", - "settings_mode_id_placeholder": "20030", - "settings_language": "Idioma", - "settings_theme_toggle": "Alternar claro / oscuro", - "settings_orca_profiles_label": "Perfiles de OrcaSlicer", - "settings_orca_profiles_import": "Importar perfiles", - "skip_confirm_btn": "Omitir", - "btn_delete": "Eliminar", - "log_print_start": "Inicio de impresión:", - "log_print_action": "Impresión:", - "log_delete_failed": "Error al eliminar" + "ss_name": "A–Z Nombre", + "store_delete_confirm": "¿Eliminar archivo?", + "store_download": "⬇ Descargar", + "store_empty": "Aún no hay archivos subidos.", + "store_estimate": "Estimación", + "store_never": "nunca impreso", + "store_no_results": "No se encontraron archivos.", + "store_print": "▶ Imprimir", + "store_print_confirm": "¿Imprimir archivo?", + "store_refresh": "↻ Actualizar", + "store_search_placeholder": "🔍 Buscar…", + "store_upload_busy": "⏳ Subiendo…", + "store_upload_error": "✗ {error}", + "store_upload_label_browse": "buscar", + "store_upload_label_prefix": "Arrastra el GCode aquí o ", + "store_upload_only_gcode": "✗ Solo se permiten archivos GCode (.gcode, .3mf, .bgcode)", + "store_upload_success": "✓ {file}", + "store_web_verify_abort": "Cancelar", + "store_web_verify_confirm": "Confirmar", + "store_web_verify_msg": "Verifica que este archivo fue hecho para Anycubic Kobra X.", + "store_web_verify_title": "Verificar archivo", + "update_apply": "Instalar ahora", + "update_applying": "Descargando...", + "update_available": "disponible", + "update_check": "Buscar actualizaciones", + "update_checking": "Comprobando...", + "update_error": "Error", + "update_none": "Ya actualizado", + "update_restarting": "Reiniciando..." } diff --git a/web/translations/fr.json b/web/translations/fr.json index cdbd0bc..ca7f147 100644 --- a/web/translations/fr.json +++ b/web/translations/fr.json @@ -1,328 +1,332 @@ { - "header_status_standby": "Prêt", - "header_status_printing": "Impression", - "header_status_complete": "Terminé", - "header_status_error": "Erreur", - "kobra_free": "Disponible", - "kobra_busy": "Occupé", - "kobra_printing": "Impression", - "kobra_preheating": "Préchauffage", - "kobra_auto_leveling": "Mise à niveau auto", - "kobra_checking": "Vérification", - "kobra_updated": "Mise à jour", - "kobra_init": "Initialisation", - "kobra_pausing": "Pause en cours…", - "kobra_paused": "En pause", - "kobra_resuming": "Reprise en cours…", - "kobra_resumed": "Repris", - "kobra_stopping": "Arrêt en cours…", - "kobra_stoped": "Arrêté", - "kobra_finished": "Terminé", - "kobra_failed": "Erreur", - "kobra_canceled": "Annulé", - "kobra_offline": "Hors ligne", - "nav_dashboard": "Tableau de bord", - "nav_print": "Impression", - "nav_temps": "Températures", - "nav_motion": "Mouvement", - "nav_ams": "AMS", - "nav_extras": "Lumière / Ventilateur", - "nav_console": "Console", - "card_progress": "Progression", - "card_temps": "Températures", - "card_light_fan": "Ventilateur", - "card_speed": "Vitesse d'impression", - "card_cam": "Caméra", - "lbl_elapsed": "Écoulé :", - "lbl_remaining": "Restant :", - "lbl_slicer_time": "Estimation slicer :", - "lbl_layers": "Couche", - "lbl_zpos": "Z (mm)", - "speed_silent": "🐢 Silencieux", - "speed_normal": "⚡ Normal", - "speed_sport": "🚀 Sport", - "lbl_light": "💡 Lumière", - "lbl_feed": "Charger", - "lbl_unload": "Décharger", - "card_ace_dry": "Séchage ACE", + "ace_dry_auto_refill": "Remplissage auto", + "ace_dry_chart": "Historique (Temp/Humidité)", + "ace_dry_current_temp": "Température", + "ace_dry_dialog_cancel": "Annuler", + "ace_dry_dialog_confirm": "Confirmer", + "ace_dry_dialog_custom_name": "Nom personnalisé", + "ace_dry_dialog_reset_default": "Réinitialiser", + "ace_dry_dialog_save_restart": "Enregistrer et redémarrer", + "ace_dry_dialog_temp": "Température (30-80°C)", + "ace_dry_dialog_time": "Temps restant (h:m:s)", + "ace_dry_dialog_title": "Réglages Temp/Durée du séchoir", "ace_dry_dryer": "Séchoir", + "ace_dry_duration": "Durée (min)", + "ace_dry_enable": "Activer le séchage", + "ace_dry_humidity": "Humidité", + "ace_dry_preset_abs_asa": "ABS / ASA", + "ace_dry_preset_custom": "Personnalisé", + "ace_dry_preset_pa_pc": "PA / PC", + "ace_dry_preset_petg": "PETG", + "ace_dry_preset_pla": "PLA", + "ace_dry_preset_pla_plus": "PLA+", + "ace_dry_preset_tpu": "TPU", + "ace_dry_start": "▶ Démarrer", "ace_dry_status_off": "Statut : Arrêté", "ace_dry_status_on": "Statut : Actif", "ace_dry_status_remaining": "Restant", - "ace_dry_humidity": "Humidité", - "ace_dry_current_temp": "Température", - "ace_dry_chart": "Historique (Temp/Humidité)", - "ace_dry_temp": "Température (°C)", - "ace_dry_duration": "Durée (min)", - "ace_dry_start": "▶ Démarrer", "ace_dry_stop": "■ Arrêter", - "ace_dry_auto_refill": "Remplissage auto", - "ace_dry_enable": "Activer le séchage", + "ace_dry_temp": "Température (°C)", "ace_dry_temp_line": "Température de séchage", "ace_dry_time_line": "Durée de séchage", "ace_dry_ui_pending": "(Interface seule, backend suivant)", - "ace_dry_dialog_title": "Réglages Temp/Durée du séchoir", - "ace_dry_dialog_temp": "Température (30-80°C)", - "ace_dry_dialog_time": "Temps restant (h:m:s)", - "ace_dry_dialog_confirm": "Confirmer", - "ace_dry_dialog_cancel": "Annuler", - "ace_dry_dialog_save_restart": "Enregistrer et redémarrer", - "ace_dry_dialog_custom_name": "Nom personnalisé", - "ace_dry_dialog_reset_default": "Réinitialiser", - "cam_placeholder": "📷 Caméra non démarrée", - "cam_stream_unavailable": "Flux indisponible", + "add_printer": "Ajouter une imprimante", + "ams_empty": "Vide", + "ams_no_data": "Aucune donnée AMS reçue", + "apd_cancel": "Annuler", + "apd_confirm": "Ajouter", + "apd_err_ip": "Veuillez saisir une adresse IP", + "apd_fetching": "Récupération des données de l'imprimante…", + "apd_lbl_ip": "IP de l'imprimante", + "apd_lbl_name": "Nom (optionnel)", + "apd_placeholder_name": "ex. Kobra X Salon", + "apd_success": "Imprimante ajoutée, redémarrage du bridge…", + "apd_title": "Ajouter une imprimante", "btn_cam_start": "▶ Caméra", + "btn_cam_start2": "▶ Démarrer", "btn_cam_stop": "◼ Caméra", + "btn_cam_stop2": "◼ Arrêter", + "btn_cancel": "✕ Arrêter", + "btn_cancel_generic": "Annuler", + "btn_confirm_generic": "Confirmer", + "btn_connect": "⚡ Connecter", + "btn_delete": "Supprimer", + "btn_disable_motors": "Moteurs Off", + "btn_disconnect": "✕ Déconnecter", + "btn_home_all": "Origine Tout", + "btn_home_xy": "Origine XY", + "btn_home_z": "Origine Z", "btn_pause": "⏸ Pause", "btn_resume": "▶ Reprendre", - "btn_cancel": "✕ Arrêter", - "label_nozzle": "Buse", + "cam_placeholder": "📷 Caméra non démarrée", + "cam_stream_unavailable": "Flux indisponible", + "card_ace_dry": "Séchage ACE", + "card_ams": "Filament", + "card_cam": "Caméra", + "card_light_fan": "Ventilateur", + "card_progress": "Progression", + "card_speed": "Vitesse d'impression", + "card_temps": "Températures", + "confirm_cancel": "Vraiment annuler l'impression ?", + "fd_cancel": "Annuler", + "fd_no_matching_material": "Aucun matériau correspondant", + "fd_no_slots_msg": "Aucun slot AMS chargé.{br}Lancer l'impression quand même ?", + "fd_objects_hint": "Ignorer des objets (optionnel) :", + "fd_objects_toggle": "Ignorer des objets", + "fd_options_title": "Options", + "fd_print": "▶ Imprimer", + "fd_slot": "Slot", + "fd_slots_hint": "Associer le canal GCode au slot AMS :", + "fd_title": "Attribution de fente", + "fd_used": "UTILISÉ", + "file_cancel_btn": "✕ Annuler", + "file_ready_btn": "▶ Lancer l'impression", + "file_slots_btn": "🎨 Choisir les slots", + "header_status_complete": "Terminé", + "header_status_error": "Erreur", + "header_status_printing": "Impression", + "header_status_standby": "Prêt", + "hint_ip_no_port": "Adresse IP uniquement, sans port (ex. 192.168.1.102)", + "kobra_auto_leveling": "Mise à niveau auto", + "kobra_busy": "Occupé", + "kobra_canceled": "Annulé", + "kobra_checking": "Vérification", + "kobra_failed": "Erreur", + "kobra_finished": "Terminé", + "kobra_free": "Disponible", + "kobra_init": "Initialisation", + "kobra_offline": "Hors ligne", + "kobra_paused": "En pause", + "kobra_pausing": "Pause en cours…", + "kobra_preheating": "Préchauffage", + "kobra_printing": "Impression", + "kobra_resumed": "Repris", + "kobra_resuming": "Reprise en cours…", + "kobra_stoped": "Arrêté", + "kobra_stopping": "Arrêt en cours…", + "kobra_updated": "Mise à jour", "label_bed": "Plateau", "label_fan": "🌀 Ventilateur", "label_light": "💡 Lumière", - "label_on_off": "On / Off", - "label_speed": "Vitesse", - "panel_print_title": "Contrôle impression", - "panel_print_btn_pause": "⏸ Pause", - "panel_print_btn_resume": "▶ Reprendre", - "panel_print_btn_cancel": "✕ Annuler", - "panel_print_temps_live": "Températures (en direct)", - "label_set": "Définir", + "label_nozzle": "Buse", "label_off": "Éteint", - "panel_temps_nozzle": "Buse", - "panel_temps_bed": "Plateau chauffant", - "panel_temps_chart": "Historique (60 dernières valeurs)", - "label_target_c": "Cible :", - "panel_motion_xy": "Axes XY", - "panel_motion_z": "Axe Z", - "label_step": "Pas :", - "btn_home_z": "Origine Z", - "btn_home_xy": "Origine XY", - "btn_home_all": "Origine Tout", - "btn_disable_motors": "Moteurs Off", - "panel_ams_title": "Filament", - "card_ams": "Filament", - "ams_no_data": "Aucune donnée AMS reçue", + "label_on_off": "On / Off", + "label_set": "Définir", "label_slot": "Slot", - "ams_empty": "Vide", - "panel_extras_light": "Lumière", - "panel_extras_fan": "Ventilateur", - "panel_extras_camera": "Caméra", - "btn_cam_start2": "▶ Démarrer", - "btn_cam_stop2": "◼ Arrêter", - "panel_console_title": "Journal d'événements", - "log_light_on": "Lumière allumée", - "log_light_off": "Lumière éteinte", - "log_fan": "Ventilateur →", - "log_nozzle": "Buse →", - "log_bed": "Plateau →", + "label_speed": "Vitesse", + "label_step": "Pas :", + "label_target_c": "Cible :", + "lbl_conn_error": "Erreur de connexion :", + "lbl_elapsed": "Écoulé :", + "lbl_feed": "Charger", + "lbl_layers": "Couche", + "lbl_light": "💡 Lumière", + "lbl_remaining": "Restant :", + "lbl_slicer_time": "Estimation slicer :", + "lbl_unload": "Décharger", + "lbl_zpos": "Z (mm)", + "log_auto": "⬇ Auto", "log_axis": "Axe", - "log_home": "Origine", - "log_home_all": "Origine Tout", + "log_bed": "Plateau →", "log_cam_start": "Caméra démarrée :", "log_cam_stop": "Caméra arrêtée", - "log_poll_error": "Erreur de sondage :", + "log_clear": "✕ Effacer", + "log_delete_failed": "Échec de la suppression", + "log_dir_all": "Tout", + "log_dir_label": "Sens :", + "log_dir_rx": "RX", + "log_dir_tx": "TX", + "log_download": "⬇ Télécharger", "log_error": "Erreur :", - "confirm_cancel": "Vraiment annuler l'impression ?", - "settings_title": "Paramètres", - "settings_connection": "Connexion", - "settings_print": "Paramètres d'impression", - "settings_poll": "Intervalle de sondage (secondes)", + "log_fan": "Ventilateur →", + "log_filter_placeholder": "Filtrer…", + "log_home": "Origine", + "log_home_all": "Origine Tout", + "log_light_off": "Lumière éteinte", + "log_light_on": "Lumière allumée", + "log_lvl_err": "⛔ Erreurs", + "log_lvl_label": "Niveau :", + "log_lvl_warn": "⚠ Avert.", + "log_nozzle": "Buse →", + "log_poll_error": "Erreur de sondage :", + "log_print_action": "Impression :", + "log_print_start": "Début de l'impression :", + "log_topic_ams": "AMS", + "log_topic_info": "Info", + "log_topic_label": "Sujet :", + "log_topic_print": "Impression", + "log_topic_status": "Statut", + "nav_ams": "AMS", + "nav_browser": "Navigateur", + "nav_console": "Console", + "nav_dashboard": "Tableau de bord", + "nav_extras": "Lumière / Ventilateur", + "nav_motion": "Mouvement", + "nav_print": "Impression", + "nav_printers": "Imprimantes", "nav_settings": "Paramètres", - "settings_cat_display": "Apparence", - "settings_cat_filament": "Filament", - "settings_cat_language": "Langue", - "settings_cat_theme": "Basculer clair / sombre", - "settings_filament_mapping": "Mappage du profil de filament (par emplacement)", - "settings_filament_mapping_save": "Enregistrer le mappage", - "settings_visible_vendors": "Fabricants visibles (liste des profils)", - "settings_visible_vendors_hint": "Seuls ces fabricants apparaissent dans la liste des profils d'emplacement. Rien de sélectionné = tout afficher. « Generic » et vos propres profils sont toujours visibles.", - "settings_visible_vendors_save": "Enregistrer la sélection", - "progress_action_print": "Imprimer", - "progress_action_slots": "Affecter les emplacements", - "progress_action_clear": "Vider", - "settings_version": "Version", - "settings_save": "Enregistrer et redémarrer", - "settings_printer_name": "Nom de l'imprimante", - "settings_printer_ip": "IP de l'imprimante", - "settings_mqtt_port": "Port MQTT", - "settings_username": "Nom d'utilisateur MQTT", - "settings_password": "Mot de passe MQTT", - "settings_device_id": "ID de l'appareil", - "settings_mode_id": "ID du mode", - "hint_ip_no_port": "Adresse IP uniquement, sans port (ex. 192.168.1.102)", - "settings_default_slot": "Slot par défaut (couleur unique)", - "settings_slot_auto": "Auto (tous les slots chargés)", - "settings_auto_leveling": "Mise à niveau auto avant impression", - "settings_camera_on_print": "Activer la caméra au démarrage de l'impression", - "settings_web_upload_warning": "Afficher un avertissement lors de l'impression de fichiers web", - "update_check": "Vérifier les mises à jour", - "update_checking": "Vérification…", - "update_available": "disponible", - "update_none": "Déjà à jour", - "update_apply": "Installer maintenant", - "update_applying": "Téléchargement…", - "update_restarting": "Redémarrage…", - "update_error": "Erreur", - "btn_connect": "⚡ Connecter", - "btn_disconnect": "✕ Déconnecter", - "lbl_conn_error": "Erreur de connexion :", - "slot_edit_title": "Modifier le slot", - "slot_edit_color": "Couleur", - "slot_edit_material": "Matériau", - "slot_edit_load": "⬇ Charger", - "slot_edit_unload": "⬆ Décharger", - "slot_edit_save": "💾 Enregistrer", - "slot_edit_custom": "ex. PLA, PETG, ABS…", - "slot_edit_ok": "Slot AMS", - "slot_edit_profile": "Profil OrcaSlicer", - "slot_edit_profile_hint": "Envoyé lors de la synchronisation OrcaSlicer comme marque spécifique au lieu de \"Générique\"", - "slot_edit_profile_default": "— Générique (défaut) —", - "orca_profile_section": "Profils OrcaSlicer", + "nav_temps": "Températures", + "orca_profile_done": "Importé", + "orca_profile_dropmsg": "Déposez ici ou cliquez", + "orca_profile_help_html": "Déposez un ZIP de votre dossier filament OrcaSlicer ou des fichiers .json individuels.
Dans OrcaSlicer : Aide → Afficher le dossier de configuration → user/<id>/filament/", "orca_profile_hint": "Importez vos propres profils de filament OrcaSlicer (ouvrez le dossier utilisateur via Aide → Afficher le dossier de configuration)", "orca_profile_import_btn": "Importer des profils", "orca_profile_import_link": "★ Importer mes profils…", "orca_profile_import_title": "Importer vos profils OrcaSlicer", - "orca_profile_help_html": "Déposez un ZIP de votre dossier filament OrcaSlicer ou des fichiers .json individuels.
Dans OrcaSlicer : Aide → Afficher le dossier de configuration → user/<id>/filament/", - "orca_profile_dropmsg": "Déposez ici ou cliquez", "orca_profile_list_label": "Profils importés", - "orca_profile_user_label": "Mes profils", - "orca_profile_user_empty": "– aucun –", - "orca_profile_uploading": "Envoi en cours…", - "orca_profile_done": "Importé", + "orca_profile_section": "Profils OrcaSlicer", "orca_profile_skipped": "ignoré", - "log_dir_all": "Tout", - "log_lvl_label": "Niveau :", - "file_ready_btn": "▶ Lancer l'impression", - "file_slots_btn": "🎨 Choisir les slots", - "file_cancel_btn": "✕ Annuler", - "nav_printers": "Imprimantes", - "skip_title": "✂ Ignorer des objets", - "skip_hint": "Décochez les objets que vous ne souhaitez plus imprimer :", - "skip_btn_label": "Objets", - "skip_no_objects": "Aucun objet dans cette impression.", + "orca_profile_uploading": "Envoi en cours…", + "orca_profile_user_empty": "– aucun –", + "orca_profile_user_label": "Mes profils", + "panel_ams_title": "Filament", + "panel_browser_title": "Explorateur de fichiers", + "panel_console_title": "Journal d'événements", + "panel_extras_camera": "Caméra", + "panel_extras_fan": "Ventilateur", + "panel_extras_light": "Lumière", + "panel_motion_xy": "Axes XY", + "panel_motion_z": "Axe Z", + "panel_print_btn_cancel": "✕ Annuler", + "panel_print_btn_pause": "⏸ Pause", + "panel_print_btn_resume": "▶ Reprendre", + "panel_print_temps_live": "Températures (en direct)", + "panel_print_title": "Contrôle impression", + "panel_temps_bed": "Plateau chauffant", + "panel_temps_chart": "Historique (60 dernières valeurs)", + "panel_temps_nozzle": "Buse", + "print_auto_leveling": "Mise à niveau auto pour cette impression", + "printers_active": "● actif", + "printers_current": "Imprimante actuelle", + "printers_empty_hint": "Aucune imprimante configurée.", + "printers_loading": "Chargement…", + "printers_none": "Aucune imprimante configurée.", + "printers_remove": "Supprimer l'imprimante", + "printers_remove_confirm": "Supprimer l'imprimante \"{name}\" ? Le bridge va redémarrer.", + "printers_switch": "Changer →", + "progress_action_clear": "Vider", + "progress_action_print": "Imprimer", + "progress_action_slots": "Affecter les emplacements", + "settings_auto_leveling": "Mise à niveau auto avant impression", + "settings_btn_tooltip": "Paramètres", + "settings_camera_on_print": "Activer la caméra au démarrage de l'impression", + "settings_cat_connection": "Connexion", + "settings_cat_display": "Apparence", + "settings_cat_filament": "Filament", + "settings_cat_language": "Langue", + "settings_cat_notifications": "Notifications", + "settings_cat_printer": "Imprimante", + "settings_cat_system": "Système", + "settings_cat_theme": "Basculer clair / sombre", + "settings_connection": "Connexion", + "settings_default_slot": "Slot par défaut (couleur unique)", + "settings_device_id": "ID de l'appareil", + "settings_device_id_hint": "32 caractères hexadécimaux", + "settings_device_id_placeholder": "32 caractères hexadécimaux", + "settings_filament_mapping": "Mappage du profil de filament (par emplacement)", + "settings_filament_mapping_save": "Enregistrer le mappage", + "settings_file_ready_banner": "Barre d'impression", + "settings_file_ready_dialog": "Dialogue d'impression", + "settings_file_ready_mode": "Après téléchargement : Comportement de démarrage d'impression", + "settings_language": "Langue", + "settings_mode_id": "ID du mode", + "settings_mode_id_placeholder": "20030", + "settings_mqtt_port": "Port MQTT", + "settings_mqtt_username_placeholder": "userXXXXXXXX", + "settings_notif_add": "Ajouter une notification", + "settings_notif_empty": "Aucune notification configurée.", + "settings_notif_ev_cancelled": "Annulé", + "settings_notif_ev_failed": "Erreur", + "settings_notif_ev_finished": "Terminé", + "settings_notif_ev_paused": "Pausé", + "settings_notif_ev_progress": "Progression", + "settings_notif_ev_started": "Démarré", + "settings_notif_interval_lbl": "Intervalle de répétition", + "settings_notif_layers_unit": "couches", + "settings_notif_min_unit": "min", + "settings_notif_send_image": "Image", + "settings_notif_test": "Test", + "settings_notif_test_fail": "Erreur", + "settings_notif_test_ok": "Envoyé", + "settings_notif_zero_off": "(0 = désactivé)", + "settings_notifications": "Notifications", + "settings_notifications_hint": "Envoyer des notifications via des URLs Apprise (discord://, telegram://, gotify://, slack://, …)", + "settings_orca_profiles_import": "Importer des profils", + "settings_orca_profiles_label": "Profils OrcaSlicer", + "settings_password": "Mot de passe MQTT", + "settings_poll": "Intervalle de sondage (secondes)", + "settings_print": "Paramètres d'impression", + "settings_printer_ip": "IP de l'imprimante", + "settings_printer_name": "Nom de l'imprimante", + "settings_printer_name_placeholder": "p. ex. Kobra X Salon", + "settings_save": "Enregistrer et redémarrer", + "settings_slot_auto": "Auto (tous les slots chargés)", + "settings_theme_toggle": "Basculer clair / sombre", + "settings_title": "Paramètres", + "settings_username": "Nom d'utilisateur MQTT", + "settings_version": "Version", + "settings_visible_vendors": "Fabricants visibles (liste des profils)", + "settings_visible_vendors_hint": "Seuls ces fabricants apparaissent dans la liste des profils d'emplacement. Rien de sélectionné = tout afficher. « Generic » et vos propres profils sont toujours visibles.", + "settings_visible_vendors_save": "Enregistrer la sélection", + "settings_web_upload_warning": "Afficher un avertissement lors de l'impression de fichiers web", + "sf_all": "Tout", + "sf_err": "✗ Échoués", + "sf_new": "Nouveau", + "sf_ok": "✓ Terminés", "skip_already": "ignoré", + "skip_btn_label": "Objets", + "skip_cancel": "Annuler", + "skip_confirm": "Ignorer", + "skip_confirm_btn": "Ignorer", + "skip_hint": "Décochez les objets que vous ne souhaitez plus imprimer :", + "skip_no_objects": "Aucun objet dans cette impression.", "skip_select_at_least_one": "Veuillez sélectionner au moins un objet.", "skip_sending": "Envoi …", "skip_success": "Les objets seront ignorés.", - "fd_objects_hint": "Ignorer des objets (optionnel) :", - "fd_objects_toggle": "Ignorer des objets", - "fd_slots_hint": "Associer le canal GCode au slot AMS :", - "fd_cancel": "Annuler", - "fd_print": "▶ Imprimer", - "fd_no_slots_msg": "Aucun slot AMS chargé.{br}Lancer l'impression quand même ?", - "fd_slot": "Slot", - "fd_no_matching_material": "Aucun matériau correspondant", - "fd_used": "UTILISÉ", - "add_printer": "Ajouter une imprimante", - "apd_lbl_ip": "IP de l'imprimante", - "apd_lbl_name": "Nom (optionnel)", - "apd_placeholder_name": "ex. Kobra X Salon", - "apd_cancel": "Annuler", - "apd_confirm": "Ajouter", - "apd_fetching": "Récupération des données de l'imprimante…", - "apd_success": "Imprimante ajoutée, redémarrage du bridge…", - "apd_err_ip": "Veuillez saisir une adresse IP", - "printers_remove": "Supprimer l'imprimante", - "printers_remove_confirm": "Supprimer l'imprimante \"{name}\" ? Le bridge va redémarrer.", - "printers_active": "● actif", - "printers_switch": "Changer →", - "printers_current": "Imprimante actuelle", - "printers_loading": "Chargement…", - "printers_none": "Aucune imprimante configurée.", - "printers_empty_hint": "Aucune imprimante configurée.", - "nav_browser": "Navigateur", - "panel_browser_title": "Explorateur de fichiers", - "store_search_placeholder": "🔍 Rechercher…", - "store_empty": "Aucun fichier uploadé.", - "store_refresh": "↻ Actualiser", - "store_print": "▶ Imprimer", - "store_download": "⬇ Télécharger", - "store_delete_confirm": "Supprimer le fichier ?", - "store_print_confirm": "Imprimer le fichier ?", - "store_web_verify_title": "Vérifier le fichier", - "store_web_verify_msg": "Veuillez vérifier que ce fichier a été créé pour l'Anycubic Kobra X.", - "store_web_verify_confirm": "Confirmer", - "store_web_verify_abort": "Annuler", - "store_no_results": "Aucun fichier trouvé.", - "store_never": "jamais imprimé", - "store_estimate": "Estimation", - "store_upload_label_prefix": "Déposez un GCode ici ou ", - "store_upload_label_browse": "parcourir", - "store_upload_busy": "⏳ Envoi en cours…", - "store_upload_success": "✓ {file}", - "store_upload_error": "✗ {error}", - "store_upload_only_gcode": "✗ Seuls les fichiers GCode sont autorisés (.gcode, .3mf, .bgcode)", - "sf_all": "Tout", - "sf_ok": "✓ Terminés", - "sf_err": "✗ Échoués", - "sf_new": "Nouveau", + "skip_title": "✂ Ignorer des objets", + "slot_edit_color": "Couleur", + "slot_edit_custom": "ex. PLA, PETG, ABS…", + "slot_edit_load": "⬇ Charger", + "slot_edit_material": "Matériau", + "slot_edit_ok": "Slot AMS", + "slot_edit_profile": "Profil OrcaSlicer", + "slot_edit_profile_default": "— Générique (défaut) —", + "slot_edit_profile_hint": "Envoyé lors de la synchronisation OrcaSlicer comme marque spécifique au lieu de \"Générique\"", + "slot_edit_save": "💾 Enregistrer", + "slot_edit_title": "Modifier le slot", + "slot_edit_unload": "⬆ Décharger", + "speed_normal": "⚡ Normal", + "speed_silent": "🐢 Silencieux", + "speed_sport": "🚀 Sport", "ss_date": "↓ Date", - "ss_name": "A–Z Nom", "ss_dur": "⏱ Durée d'impression", - "ace_dry_preset_pla": "PLA", - "ace_dry_preset_pla_plus": "PLA+", - "ace_dry_preset_petg": "PETG", - "ace_dry_preset_tpu": "TPU", - "ace_dry_preset_abs_asa": "ABS / ASA", - "ace_dry_preset_pa_pc": "PA / PC", - "ace_dry_preset_custom": "Personnalisé", - "fd_options_title": "Options", - "print_auto_leveling": "Mise à niveau auto pour cette impression", - "settings_file_ready_mode": "Démarrer le dialogue d'impression", - "settings_file_ready_banner": "Barre d'impression", - "settings_file_ready_dialog": "Dialogue d'impression", - "log_dir_rx": "RX", - "log_dir_tx": "TX", - "log_dir_label": "Sens :", - "log_lvl_err": "⛔ Erreurs", - "log_lvl_warn": "⚠ Avert.", - "log_topic_label": "Sujet :", - "log_topic_ams": "AMS", - "log_topic_print": "Impression", - "log_topic_info": "Info", - "log_topic_status": "Statut", - "log_download": "⬇ Télécharger", - "log_auto": "⬇ Auto", - "log_clear": "✕ Effacer", - "log_filter_placeholder": "Filtrer…", - "skip_cancel": "Annuler", - "skip_confirm": "Ignorer", - "settings_cat_connection": "Connexion", - "settings_cat_printer": "Imprimante", - "settings_cat_notifications": "Notifications", - "settings_cat_system": "Système", - "settings_notifications": "Notifications", - "settings_notifications_hint": "Envoyer des notifications via des URLs Apprise (discord://, telegram://, gotify://, slack://, …)", - "settings_notif_add": "Ajouter une notification", - "settings_notif_empty": "Aucune notification configurée.", - "settings_notif_test": "Test", - "settings_notif_test_ok": "Envoyé", - "settings_notif_test_fail": "Erreur", - "settings_notif_ev_finished": "Terminé", - "settings_notif_ev_failed": "Erreur", - "settings_notif_ev_cancelled": "Annulé", - "settings_notif_ev_paused": "Pausé", - "settings_notif_ev_started": "Démarré", - "settings_notif_ev_progress": "Progression", - "settings_notif_interval_lbl": "Intervalle de répétition", - "settings_notif_min_unit": "min", - "settings_notif_layers_unit": "couches", - "settings_notif_zero_off": "(0 = désactivé)", - "settings_notif_send_image": "Image", - "settings_btn_tooltip": "Paramètres", - "settings_printer_name_placeholder": "p. ex. Kobra X Salon", - "settings_device_id_placeholder": "32 caractères hexadécimaux", - "settings_mqtt_username_placeholder": "userXXXXXXXX", - "settings_device_id_hint": "32 caractères hexadécimaux", - "settings_mode_id_placeholder": "20030", - "settings_language": "Langue", - "settings_theme_toggle": "Basculer clair / sombre", - "settings_orca_profiles_label": "Profils OrcaSlicer", - "settings_orca_profiles_import": "Importer des profils", - "skip_confirm_btn": "Ignorer", - "btn_delete": "Supprimer", - "log_print_start": "Début de l'impression :", - "log_print_action": "Impression :", - "log_delete_failed": "Échec de la suppression" + "ss_name": "A–Z Nom", + "store_delete_confirm": "Supprimer le fichier ?", + "store_download": "⬇ Télécharger", + "store_empty": "Aucun fichier uploadé.", + "store_estimate": "Estimation", + "store_never": "jamais imprimé", + "store_no_results": "Aucun fichier trouvé.", + "store_print": "▶ Imprimer", + "store_print_confirm": "Imprimer le fichier ?", + "store_refresh": "↻ Actualiser", + "store_search_placeholder": "🔍 Rechercher…", + "store_upload_busy": "⏳ Envoi en cours…", + "store_upload_error": "✗ {error}", + "store_upload_label_browse": "parcourir", + "store_upload_label_prefix": "Déposez un GCode ici ou ", + "store_upload_only_gcode": "✗ Seuls les fichiers GCode sont autorisés (.gcode, .3mf, .bgcode)", + "store_upload_success": "✓ {file}", + "store_web_verify_abort": "Annuler", + "store_web_verify_confirm": "Confirmer", + "store_web_verify_msg": "Veuillez vérifier que ce fichier a été créé pour Anycubic Kobra X.", + "store_web_verify_title": "Vérifier le fichier", + "update_apply": "Installer maintenant", + "update_applying": "Téléchargement…", + "update_available": "disponible", + "update_check": "Vérifier les mises à jour", + "update_checking": "Vérification…", + "update_error": "Erreur", + "update_none": "Déjà à jour", + "update_restarting": "Redémarrage…" } diff --git a/web/translations/it.json b/web/translations/it.json index db400cb..19fdac2 100644 --- a/web/translations/it.json +++ b/web/translations/it.json @@ -1,328 +1,330 @@ { - "header_status_standby": "Pronto", - "header_status_printing": "In stampa", - "header_status_complete": "Completato", - "header_status_error": "Errore", - "kobra_free": "Pronto", - "kobra_busy": "Occupato", - "kobra_printing": "In stampa", - "kobra_preheating": "Preriscaldamento", - "kobra_auto_leveling": "Livellamento automatico", - "kobra_checking": "Verifica", - "kobra_updated": "Aggiornamento", - "kobra_init": "Inizializzazione", - "kobra_pausing": "Pausa in corso...", - "kobra_paused": "In pausa", - "kobra_resuming": "Ripresa...", - "kobra_resumed": "Ripreso", - "kobra_stopping": "Arresto...", - "kobra_stoped": "Arrestato", - "kobra_finished": "Finito", - "kobra_failed": "Errore", - "kobra_canceled": "Annullato", - "kobra_offline": "Offline", - "nav_dashboard": "Dashboard", - "nav_print": "Stampa", - "nav_temps": "Temperature", - "nav_motion": "Movimento", - "nav_ams": "AMS", - "nav_extras": "Luce / Ventola", - "nav_console": "Console", - "card_progress": "Avanzamento", - "card_temps": "Temperature", - "card_light_fan": "Ventola", - "card_speed": "Velocità di stampa", - "card_cam": "Camera", - "lbl_elapsed": "Trascorso:", - "lbl_remaining": "Rimanente:", - "lbl_slicer_time": "Stima slicer:", - "lbl_layers": "Layer", - "lbl_zpos": "Z (mm)", - "speed_silent": "🐢 Silenzioso", - "speed_normal": "⚡ Normale", - "speed_sport": "🚀 Sport", - "lbl_light": "💡 Luce", - "lbl_feed": "Carica", - "lbl_unload": "Rimuovi", - "card_ace_dry": "Essiccazione ACE", + "ace_dry_auto_refill": "Ricarica automatica", + "ace_dry_chart": "Cronologia (Temp/Umidità)", + "ace_dry_current_temp": "Temperatura", + "ace_dry_dialog_cancel": "Annulla", + "ace_dry_dialog_confirm": "Conferma", + "ace_dry_dialog_custom_name": "Nome personalizzato", + "ace_dry_dialog_reset_default": "Ripristina predefiniti", + "ace_dry_dialog_save_restart": "Salva e riavvia", + "ace_dry_dialog_temp": "Temperatura (30-80°C)", + "ace_dry_dialog_time": "Tempo rim. (h:m:s)", + "ace_dry_dialog_title": "Impostazioni Temp/Tempo essiccatore", "ace_dry_dryer": "Essiccatore", + "ace_dry_duration": "Durata (min)", + "ace_dry_enable": "Abilita essiccazione", + "ace_dry_humidity": "Umidità", + "ace_dry_preset_abs_asa": "ABS / ASA", + "ace_dry_preset_custom": "Personalizzato", + "ace_dry_preset_pa_pc": "PA / PC", + "ace_dry_preset_petg": "PETG", + "ace_dry_preset_pla": "PLA", + "ace_dry_preset_pla_plus": "PLA+", + "ace_dry_preset_tpu": "TPU", + "ace_dry_start": "▶ Avvia", "ace_dry_status_off": "Stato: Spento", "ace_dry_status_on": "Stato: Attivo", "ace_dry_status_remaining": "Rimanente", - "ace_dry_humidity": "Umidità", - "ace_dry_current_temp": "Temperatura", - "ace_dry_chart": "Cronologia (Temp/Umidità)", - "ace_dry_temp": "Temperatura (°C)", - "ace_dry_duration": "Durata (min)", - "ace_dry_start": "▶ Avvia", "ace_dry_stop": "■ Ferma", - "ace_dry_auto_refill": "Ricarica automatica", - "ace_dry_enable": "Abilita essiccazione", + "ace_dry_temp": "Temperatura (°C)", "ace_dry_temp_line": "Temperatura di essiccazione", "ace_dry_time_line": "Tempo di essiccazione", "ace_dry_ui_pending": "(Solo interfaccia, backend a seguire)", - "ace_dry_dialog_title": "Impostazioni Temp/Tempo essiccatore", - "ace_dry_dialog_temp": "Temperatura (30-80°C)", - "ace_dry_dialog_time": "Tempo rim. (h:m:s)", - "ace_dry_dialog_confirm": "Conferma", - "ace_dry_dialog_cancel": "Annulla", - "ace_dry_dialog_save_restart": "Salva e riavvia", - "ace_dry_dialog_custom_name": "Nome personalizzato", - "ace_dry_dialog_reset_default": "Ripristina predefiniti", - "ace_dry_preset_pla": "PLA", - "ace_dry_preset_pla_plus": "PLA+", - "ace_dry_preset_petg": "PETG", - "ace_dry_preset_tpu": "TPU", - "ace_dry_preset_abs_asa": "ABS / ASA", - "ace_dry_preset_pa_pc": "PA / PC", - "ace_dry_preset_custom": "Personalizzato", - "cam_placeholder": "📷 Camera non avviata", - "cam_stream_unavailable": "Flusso video non disponibile", + "add_printer": "Aggiungi stampante", + "ams_empty": "Vuoto", + "ams_no_data": "Nessun dato ricevuto dall' AMS", + "apd_cancel": "Annulla", + "apd_confirm": "Aggiungi", + "apd_err_ip": "Inserisci un indirizzo IP", + "apd_fetching": "Recupero dati dalla stampante…", + "apd_lbl_ip": "IP stampante", + "apd_lbl_name": "Nome (opzionale)", + "apd_placeholder_name": "es. Kobra X Soggiorno", + "apd_success": "Stampante aggiunta, riavvio del bridge in corso…", + "apd_title": "Aggiungi stampante", "btn_cam_start": "▶ Camera", + "btn_cam_start2": "▶ Avvia", "btn_cam_stop": "◼ Camera", + "btn_cam_stop2": "◼ Ferma", + "btn_cancel": "✕ Stop", + "btn_cancel_generic": "Annulla", + "btn_confirm_generic": "Conferma", + "btn_connect": "⚡ Connetti", + "btn_delete": "Elimina", + "btn_disable_motors": "Spegni motori", + "btn_disconnect": "✕ Disconnetti", + "btn_home_all": "Home generale", + "btn_home_xy": "Home XY", + "btn_home_z": "Home Z", "btn_pause": "⏸ Pausa", "btn_resume": "▶ Riprendi", - "btn_cancel": "✕ Stop", - "label_nozzle": "Ugello", + "cam_placeholder": "📷 Camera non avviata", + "cam_stream_unavailable": "Flusso video non disponibile", + "card_ace_dry": "Essiccazione ACE", + "card_ams": "Filamento", + "card_cam": "Camera", + "card_light_fan": "Ventola", + "card_progress": "Avanzamento", + "card_speed": "Velocità di stampa", + "card_temps": "Temperature", + "confirm_cancel": "Annullare davvero la stampa?", + "fd_cancel": "Annulla", + "fd_no_matching_material": "Nessun materiale corrispondente", + "fd_no_slots_msg": "Nessuno slot AMS caricato.{br}Avviare comunque la stampa?", + "fd_objects_hint": "Salta oggetti (opzionale):", + "fd_objects_toggle": "Salta oggetti", + "fd_options_title": "Opzioni di stampa", + "fd_print": "▶ Stampa", + "fd_slot": "Slot", + "fd_slots_hint": "Assegna il canale GCode allo slot AMS:", + "fd_title": "Assegnazione slot", + "fd_used": "USATO", + "file_cancel_btn": "✕ Annulla", + "file_ready_btn": "▶ Avvia stampa", + "file_slots_btn": "🎨 Seleziona slot", + "header_status_complete": "Completato", + "header_status_error": "Errore", + "header_status_printing": "In stampa", + "header_status_standby": "Pronto", + "hint_ip_no_port": "Solo indirizzo IP, senza porta (es. 192.168.1.102)", + "kobra_auto_leveling": "Livellamento automatico", + "kobra_busy": "Occupato", + "kobra_canceled": "Annullato", + "kobra_checking": "Verifica", + "kobra_failed": "Errore", + "kobra_finished": "Finito", + "kobra_free": "Pronto", + "kobra_init": "Inizializzazione", + "kobra_offline": "Offline", + "kobra_paused": "In pausa", + "kobra_pausing": "Pausa in corso...", + "kobra_preheating": "Preriscaldamento", + "kobra_printing": "In stampa", + "kobra_resumed": "Ripreso", + "kobra_resuming": "Ripresa...", + "kobra_stoped": "Arrestato", + "kobra_stopping": "Arresto...", + "kobra_updated": "Aggiornamento", "label_bed": "Piatto", "label_fan": "🌀 Ventola", "label_light": "💡 Luce", - "label_on_off": "On / Off", - "label_speed": "Velocità", - "panel_print_title": "Controllo stampa", - "panel_print_btn_pause": "⏸ Pausa", - "panel_print_btn_resume": "▶ Riprendi", - "panel_print_btn_cancel": "✕ Annulla", - "panel_print_temps_live": "Temperature (In tempo reale)", - "label_set": "Imposta", + "label_nozzle": "Ugello", "label_off": "Off", - "panel_temps_nozzle": "Ugello", - "panel_temps_bed": "Piatto riscaldato", - "panel_temps_chart": "Cronologia (ultime 60 letture)", - "label_target_c": "Target:", - "panel_motion_xy": "Assi XY", - "panel_motion_z": "Asse Z", - "label_step": "Ampiezza passo:", - "btn_home_z": "Home Z", - "btn_home_xy": "Home XY", - "btn_home_all": "Home generale", - "btn_disable_motors": "Spegni motori", - "panel_ams_title": "Filamento", - "card_ams": "Filamento", - "ams_no_data": "Nessun dato ricevuto dall' AMS", + "label_on_off": "On / Off", + "label_set": "Imposta", "label_slot": "Slot", - "ams_empty": "Vuoto", - "panel_extras_light": "Luce", - "panel_extras_fan": "Ventola", - "panel_extras_camera": "Camera", - "btn_cam_start2": "▶ Avvia", - "btn_cam_stop2": "◼ Ferma", - "panel_console_title": "Registro eventi", - "log_light_on": "Luce accesa", - "log_light_off": "Luce spenta", - "log_fan": "Ventola →", - "log_nozzle": "Ugello →", - "log_bed": "Piatto →", + "label_speed": "Velocità", + "label_step": "Ampiezza passo:", + "label_target_c": "Target:", + "lbl_conn_error": "Errore di connessione:", + "lbl_elapsed": "Trascorso:", + "lbl_feed": "Carica", + "lbl_layers": "Layer", + "lbl_light": "💡 Luce", + "lbl_remaining": "Rimanente:", + "lbl_slicer_time": "Stima slicer:", + "lbl_unload": "Rimuovi", + "lbl_zpos": "Z (mm)", + "log_auto": "⬇ Auto", "log_axis": "Asse", - "log_home": "Home", - "log_home_all": "Home generale", + "log_bed": "Piatto →", "log_cam_start": "Camera avviata:", "log_cam_stop": "Camera arrestata", - "log_poll_error": "Errore di sincronizzazione:", + "log_clear": "✕ Cancella", + "log_delete_failed": "Eliminazione non riuscita", + "log_dir_all": "Tutti", + "log_dir_label": "Dir:", + "log_dir_rx": "RX", + "log_dir_tx": "TX", + "log_download": "⬇ Scarica", "log_error": "Errore:", - "confirm_cancel": "Annullare davvero la stampa?", - "settings_title": "Impostazioni", - "settings_connection": "Connessione", - "settings_print": "Impostazioni di stampa", - "settings_poll": "Intervallo di sincronizzazione (secondi)", + "log_fan": "Ventola →", + "log_filter_placeholder": "Filtra…", + "log_home": "Home", + "log_home_all": "Home generale", + "log_light_off": "Luce spenta", + "log_light_on": "Luce accesa", + "log_lvl_err": "⛔ Errori", + "log_lvl_label": "Livello:", + "log_lvl_warn": "⚠ Avvisi", + "log_nozzle": "Ugello →", + "log_poll_error": "Errore di sincronizzazione:", + "log_print_action": "Stampa:", + "log_print_start": "Inizio stampa:", + "log_topic_ams": "AMS", + "log_topic_info": "Info", + "log_topic_label": "Argomento:", + "log_topic_print": "Stampa", + "log_topic_status": "Stato", + "nav_ams": "AMS", + "nav_browser": "Browser", + "nav_console": "Console", + "nav_dashboard": "Dashboard", + "nav_extras": "Luce / Ventola", + "nav_motion": "Movimento", + "nav_print": "Stampa", + "nav_printers": "Stampanti", "nav_settings": "Impostazioni", - "settings_cat_display": "Aspetto", - "settings_cat_filament": "Filamento", - "settings_cat_language": "Lingua", - "settings_cat_theme": "Alterna chiaro / scuro", - "settings_filament_mapping": "Mappatura profilo filamento (per slot)", - "settings_filament_mapping_save": "Salva mappatura", - "settings_visible_vendors": "Produttori visibili (menu del profilo)", - "settings_visible_vendors_hint": "Solo questi produttori appariranno nel menu del profilo dello slot. Se non selezioni nulla = mostra tutti. I profili \"Generici\" e i tuoi personali sono sempre visibili.", - "settings_visible_vendors_save": "Salva selezione", - "progress_action_print": "Stampa", - "progress_action_slots": "Mappa slot", - "progress_action_clear": "Cancella", - "settings_version": "Versione", - "settings_save": "Salva e riavvia", - "settings_printer_name": "Nome stampante", - "settings_printer_ip": "IP stampante", - "settings_mqtt_port": "Porta MQTT", - "settings_username": "Nome utente MQTT", - "settings_password": "Password MQTT", - "settings_device_id": "ID dispositivo", - "settings_mode_id": "ID modalità", - "hint_ip_no_port": "Solo indirizzo IP, senza porta (es. 192.168.1.102)", - "settings_default_slot": "Slot predefinito (colore singolo)", - "settings_slot_auto": "Auto (tutti gli slot caricati)", - "settings_auto_leveling": "Livellamento automatico predefinito", - "fd_options_title": "Opzioni di stampa", - "print_auto_leveling": "Livellamento automatico", - "settings_file_ready_mode": "Comportamento all'avvio stampa", - "settings_file_ready_banner": "Barra di stampa", - "settings_file_ready_dialog": "Finestra di dialogo di stampa", - "settings_camera_on_print": "Attiva la camera all'avvio della stampa", - "settings_web_upload_warning": "Mostra un avviso quando si stampano caricamenti web", - "update_check": "Controlla aggiornamenti", - "update_checking": "Verifica in corso...", - "update_available": "disponibile", - "update_none": "Già aggiornato", - "update_apply": "Installa ora", - "update_applying": "Download in corso...", - "update_restarting": "Riavvio in corso...", - "update_error": "Errore", - "btn_connect": "⚡ Connetti", - "btn_disconnect": "✕ Disconnetti", - "lbl_conn_error": "Errore di connessione:", - "slot_edit_title": "Modifica slot", - "slot_edit_color": "Colore", - "slot_edit_material": "Materiale", - "slot_edit_load": "⬇ Carica", - "slot_edit_unload": "⬆ Rimuovi", - "slot_edit_save": "💾 Salva", - "slot_edit_custom": "es. PLA, PETG, ABS…", - "slot_edit_ok": "Slot AMS", - "slot_edit_profile": "Profilo OrcaSlicer", - "slot_edit_profile_hint": "Inviato durante la sincronizzazione con OrcaSlicer come marchio specifico invece di un semplice \"Generico\"", - "slot_edit_profile_default": "— Generico (predefinito) —", - "orca_profile_section": "Profili OrcaSlicer", + "nav_temps": "Temperature", + "orca_profile_done": "Importato", + "orca_profile_dropmsg": "Trascina qui o fai clic", + "orca_profile_help_html": "Carica un file ZIP della tua cartella filamenti di OrcaSlicer o file singoli .json.
In OrcaSlicer: Aiuto → Mostra cartella di configurazione → user/<id>/filament/", "orca_profile_hint": "Importa i tuoi profili di filamento OrcaSlicer (apri la cartella utente tramite Aiuto → Mostra cartella di configurazione)", "orca_profile_import_btn": "Importa profili", "orca_profile_import_link": "★ Importa i tuoi profili…", "orca_profile_import_title": "Importa i tuoi profili OrcaSlicer", - "orca_profile_help_html": "Carica un file ZIP della tua cartella filamenti di OrcaSlicer o file singoli .json.
In OrcaSlicer: Aiuto → Mostra cartella di configurazione → user/<id>/filament/", - "orca_profile_dropmsg": "Trascina qui o fai clic", "orca_profile_list_label": "Attualmente importati", - "orca_profile_user_label": "Profili personali", - "orca_profile_user_empty": "– nessuno –", - "orca_profile_uploading": "Caricamento in corso…", - "orca_profile_done": "Importato", + "orca_profile_section": "Profili OrcaSlicer", "orca_profile_skipped": "saltato", - "log_dir_all": "Tutti", - "log_dir_rx": "RX", - "log_dir_tx": "TX", - "log_dir_label": "Dir:", - "log_lvl_label": "Livello:", - "log_lvl_err": "⛔ Errori", - "log_lvl_warn": "⚠ Avvisi", - "log_topic_label": "Argomento:", - "log_topic_ams": "AMS", - "log_topic_print": "Stampa", - "log_topic_info": "Info", - "log_topic_status": "Stato", - "log_download": "⬇ Scarica", - "log_auto": "⬇ Auto", - "log_clear": "✕ Cancella", - "log_filter_placeholder": "Filtra…", - "file_ready_btn": "▶ Avvia stampa", - "file_slots_btn": "🎨 Seleziona slot", - "file_cancel_btn": "✕ Annulla", - "nav_printers": "Stampanti", - "skip_title": "✂ Salta oggetti", - "skip_hint": "Deseleziona gli oggetti che non vuoi più stampare:", - "skip_btn_label": "Oggetti", - "skip_no_objects": "Nessun oggetto in questa stampa.", + "orca_profile_uploading": "Caricamento in corso…", + "orca_profile_user_empty": "– nessuno –", + "orca_profile_user_label": "Profili personali", + "panel_ams_title": "Filamento", + "panel_browser_title": "Browser dei file", + "panel_console_title": "Registro eventi", + "panel_extras_camera": "Camera", + "panel_extras_fan": "Ventola", + "panel_extras_light": "Luce", + "panel_motion_xy": "Assi XY", + "panel_motion_z": "Asse Z", + "panel_print_btn_cancel": "✕ Annulla", + "panel_print_btn_pause": "⏸ Pausa", + "panel_print_btn_resume": "▶ Riprendi", + "panel_print_temps_live": "Temperature (In tempo reale)", + "panel_print_title": "Controllo stampa", + "panel_temps_bed": "Piatto riscaldato", + "panel_temps_chart": "Cronologia (ultime 60 letture)", + "panel_temps_nozzle": "Ugello", + "print_auto_leveling": "Livellamento automatico", + "printers_active": "● attiva", + "printers_current": "Stampante corrente", + "printers_empty_hint": "Nessuna stampante ancora configurata.", + "printers_loading": "Caricamento in corso…", + "printers_none": "Nessuna stampante configurata.", + "printers_remove": "Rimuovi stampante", + "printers_remove_confirm": "Rimuovere la stampante \"{name}\"? Il bridge si riavvierà.", + "printers_switch": "Cambia →", + "progress_action_clear": "Cancella", + "progress_action_print": "Stampa", + "progress_action_slots": "Mappa slot", + "settings_auto_leveling": "Livellamento automatico predefinito", + "settings_btn_tooltip": "Impostazioni", + "settings_camera_on_print": "Attiva la camera all'avvio della stampa", + "settings_cat_display": "Aspetto", + "settings_cat_filament": "Filamento", + "settings_cat_language": "Lingua", + "settings_cat_notifications": "Notifiche", + "settings_cat_system": "Sistema", + "settings_cat_theme": "Alterna chiaro / scuro", + "settings_connection": "Connessione", + "settings_default_slot": "Slot predefinito (colore singolo)", + "settings_device_id": "ID dispositivo", + "settings_device_id_hint": "32 caratteri esadecimali", + "settings_device_id_placeholder": "32 caratteri esadecimali", + "settings_filament_mapping": "Mappatura profilo filamento (per slot)", + "settings_filament_mapping_save": "Salva mappatura", + "settings_file_ready_banner": "Barra di stampa", + "settings_file_ready_dialog": "Finestra di dialogo stampa", + "settings_file_ready_mode": "Dopo il caricamento: Comportamento di avvio stampa", + "settings_language": "Lingua", + "settings_mode_id": "ID modalità", + "settings_mode_id_placeholder": "20030", + "settings_mqtt_port": "Porta MQTT", + "settings_mqtt_username_placeholder": "userXXXXXXXX", + "settings_notif_add": "Aggiungi notifica", + "settings_notif_empty": "Nessuna notifica configurata.", + "settings_notif_ev_cancelled": "Annullato", + "settings_notif_ev_failed": "Errore", + "settings_notif_ev_finished": "Completato", + "settings_notif_ev_paused": "In pausa", + "settings_notif_ev_progress": "Progresso", + "settings_notif_ev_started": "Iniziato", + "settings_notif_interval_lbl": "Intervallo di ripetizione", + "settings_notif_layers_unit": "strati", + "settings_notif_min_unit": "min", + "settings_notif_send_image": "Immagine", + "settings_notif_test": "Test", + "settings_notif_test_fail": "Errore", + "settings_notif_test_ok": "Inviato", + "settings_notif_zero_off": "(0 = disabilitato)", + "settings_notifications": "Notifiche", + "settings_notifications_hint": "Invia notifiche tramite URL Apprise (discord://, telegram://, gotify://, slack://, …)", + "settings_orca_profiles_import": "Importa profili", + "settings_orca_profiles_label": "Profili OrcaSlicer", + "settings_password": "Password MQTT", + "settings_poll": "Intervallo di sincronizzazione (secondi)", + "settings_print": "Impostazioni di stampa", + "settings_printer_ip": "IP stampante", + "settings_printer_name": "Nome stampante", + "settings_printer_name_placeholder": "p. es. Kobra X Sala", + "settings_save": "Salva e riavvia", + "settings_slot_auto": "Auto (tutti gli slot caricati)", + "settings_theme_toggle": "Attiva/disattiva chiaro / scuro", + "settings_title": "Impostazioni", + "settings_username": "Nome utente MQTT", + "settings_version": "Versione", + "settings_visible_vendors": "Produttori visibili (menu del profilo)", + "settings_visible_vendors_hint": "Solo questi produttori appariranno nel menu del profilo dello slot. Se non selezioni nulla = mostra tutti. I profili \"Generici\" e i tuoi personali sono sempre visibili.", + "settings_visible_vendors_save": "Salva selezione", + "settings_web_upload_warning": "Mostra un avviso quando si stampano caricamenti web", + "sf_all": "Tutti", + "sf_err": "✗ Fallito", + "sf_new": "Nuovo", + "sf_ok": "✓ Completato", "skip_already": "saltato", + "skip_btn_label": "Oggetti", "skip_cancel": "Annulla", "skip_confirm": "Salta", + "skip_confirm_btn": "Salta", + "skip_hint": "Deseleziona gli oggetti che non vuoi più stampare:", + "skip_no_objects": "Nessun oggetto in questa stampa.", "skip_select_at_least_one": "Seleziona almeno un oggetto.", "skip_sending": "Invio in corso …", "skip_success": "Gli oggetti verranno saltati.", - "fd_objects_hint": "Salta oggetti (opzionale):", - "fd_objects_toggle": "Salta oggetti", - "fd_slots_hint": "Assegna il canale GCode allo slot AMS:", - "fd_cancel": "Annulla", - "fd_print": "▶ Stampa", - "fd_no_slots_msg": "Nessuno slot AMS caricato.{br}Avviare comunque la stampa?", - "fd_slot": "Slot", - "fd_no_matching_material": "Nessun materiale corrispondente", - "fd_used": "USATO", - "add_printer": "Aggiungi stampante", - "apd_lbl_ip": "IP stampante", - "apd_lbl_name": "Nome (opzionale)", - "apd_placeholder_name": "es. Kobra X Soggiorno", - "apd_cancel": "Annulla", - "apd_confirm": "Aggiungi", - "apd_fetching": "Recupero dati dalla stampante…", - "apd_success": "Stampante aggiunta, riavvio del bridge in corso…", - "apd_err_ip": "Inserisci un indirizzo IP", - "printers_remove": "Rimuovi stampante", - "printers_remove_confirm": "Rimuovere la stampante \"{name}\"? Il bridge si riavvierà.", - "printers_active": "● attiva", - "printers_switch": "Cambia →", - "printers_current": "Stampante corrente", - "printers_loading": "Caricamento in corso…", - "printers_none": "Nessuna stampante configurata.", - "printers_empty_hint": "Nessuna stampante ancora configurata.", - "nav_browser": "Browser", - "panel_browser_title": "Browser dei file", - "store_search_placeholder": "🔍 Cerca…", - "store_empty": "Nessun file caricato.", - "store_refresh": "↻ Aggiorna", - "store_print": "▶ Stampa", - "store_download": "⬇ Scarica", - "store_delete_confirm": "Eliminare il file?", - "store_print_confirm": "Stampare il file?", - "store_web_verify_title": "Verifica file", - "store_web_verify_msg": "Verifica che questo file sia stato creato per Anycubic Kobra X.", - "store_web_verify_confirm": "Conferma", - "store_web_verify_abort": "Interrompi", - "store_no_results": "Nessun file trovato.", - "store_never": "mai stampato", - "store_estimate": "Stima", - "store_upload_label_prefix": "Trascina il GCode qui o ", - "store_upload_label_browse": "sfoglia", - "store_upload_busy": "⏳ Caricamento in corso…", - "store_upload_success": "✓ {file}", - "store_upload_error": "✗ {error}", - "store_upload_only_gcode": "✗ Sono consentiti solo file GCode (.gcode, .3mf, .bgcode)", - "sf_all": "Tutti", - "sf_ok": "✓ Completato", - "sf_err": "✗ Fallito", - "sf_new": "Nuovo", + "skip_title": "✂ Salta oggetti", + "slot_edit_color": "Colore", + "slot_edit_custom": "es. PLA, PETG, ABS…", + "slot_edit_load": "⬇ Carica", + "slot_edit_material": "Materiale", + "slot_edit_ok": "Slot AMS", + "slot_edit_profile": "Profilo OrcaSlicer", + "slot_edit_profile_default": "— Generico (predefinito) —", + "slot_edit_profile_hint": "Inviato durante la sincronizzazione con OrcaSlicer come marchio specifico invece di un semplice \"Generico\"", + "slot_edit_save": "💾 Salva", + "slot_edit_title": "Modifica slot", + "slot_edit_unload": "⬆ Rimuovi", + "speed_normal": "⚡ Normale", + "speed_silent": "🐢 Silenzioso", + "speed_sport": "🚀 Sport", "ss_date": "↓ Data", - "ss_name": "Nome A–Z", "ss_dur": "⏱ Tempo di stampa", - "settings_cat_connection": "Connessione", - "settings_cat_printer": "Stampante", - "settings_cat_notifications": "Notifiche", - "settings_cat_system": "Sistema", - "settings_notifications": "Notifiche", - "settings_notifications_hint": "Invia notifiche tramite URL Apprise (discord://, telegram://, gotify://, slack://, …)", - "settings_notif_add": "Aggiungi notifica", - "settings_notif_empty": "Nessuna notifica configurata.", - "settings_notif_test": "Test", - "settings_notif_test_ok": "Inviato", - "settings_notif_test_fail": "Errore", - "settings_notif_ev_finished": "Completato", - "settings_notif_ev_failed": "Errore", - "settings_notif_ev_cancelled": "Annullato", - "settings_notif_ev_paused": "In pausa", - "settings_notif_ev_started": "Iniziato", - "settings_notif_ev_progress": "Progresso", - "settings_notif_interval_lbl": "Intervallo di ripetizione", - "settings_notif_min_unit": "min", - "settings_notif_layers_unit": "strati", - "settings_notif_zero_off": "(0 = disabilitato)", - "settings_notif_send_image": "Immagine", - "settings_btn_tooltip": "Impostazioni", - "settings_printer_name_placeholder": "p. es. Kobra X Sala", - "settings_device_id_placeholder": "32 caratteri esadecimali", - "settings_mqtt_username_placeholder": "userXXXXXXXX", - "settings_device_id_hint": "32 caratteri esadecimali", - "settings_mode_id_placeholder": "20030", - "settings_language": "Lingua", - "settings_theme_toggle": "Attiva/disattiva chiaro / scuro", - "settings_orca_profiles_label": "Profili OrcaSlicer", - "settings_orca_profiles_import": "Importa profili", - "skip_confirm_btn": "Salta", - "btn_delete": "Elimina", - "log_print_start": "Inizio stampa:", - "log_print_action": "Stampa:", - "log_delete_failed": "Eliminazione non riuscita" + "ss_name": "Nome A–Z", + "store_delete_confirm": "Eliminare il file?", + "store_download": "⬇ Scarica", + "store_empty": "Nessun file caricato.", + "store_estimate": "Stima", + "store_never": "mai stampato", + "store_no_results": "Nessun file trovato.", + "store_print": "▶ Stampa", + "store_print_confirm": "Stampare il file?", + "store_refresh": "↻ Aggiorna", + "store_search_placeholder": "🔍 Cerca…", + "store_upload_busy": "⏳ Caricamento in corso…", + "store_upload_error": "✗ {error}", + "store_upload_label_browse": "sfoglia", + "store_upload_label_prefix": "Trascina il GCode qui o ", + "store_upload_only_gcode": "✗ Sono consentiti solo file GCode (.gcode, .3mf, .bgcode)", + "store_upload_success": "✓ {file}", + "store_web_verify_abort": "Annulla", + "store_web_verify_confirm": "Conferma", + "store_web_verify_msg": "Verifica che questo file sia stato creato per Anycubic Kobra X.", + "store_web_verify_title": "Verifica file", + "update_apply": "Installa ora", + "update_applying": "Download in corso...", + "update_available": "disponibile", + "update_check": "Controlla aggiornamenti", + "update_checking": "Verifica in corso...", + "update_error": "Errore", + "update_none": "Già aggiornato", + "update_restarting": "Riavvio in corso..." } diff --git a/web/translations/zh-cn.json b/web/translations/zh-cn.json index 9ac5102..542c5a2 100644 --- a/web/translations/zh-cn.json +++ b/web/translations/zh-cn.json @@ -1,328 +1,329 @@ { - "header_status_standby": "就绪", - "header_status_printing": "打印中", - "header_status_complete": "完成", - "header_status_error": "错误", - "kobra_free": "就绪", - "kobra_busy": "忙碌", - "kobra_printing": "打印中", - "kobra_preheating": "预热中", - "kobra_auto_leveling": "自动调平", - "kobra_checking": "检查中", - "kobra_updated": "更新中", - "kobra_init": "初始化中", - "kobra_pausing": "暂停中...", - "kobra_paused": "已暂停", - "kobra_resuming": "恢复中...", - "kobra_resumed": "已恢复", - "kobra_stopping": "停止中...", - "kobra_stoped": "已停止", - "kobra_finished": "已完成", - "kobra_failed": "错误", - "kobra_canceled": "已取消", - "kobra_offline": "离线", - "nav_dashboard": "仪表盘", - "nav_print": "打印", - "nav_temps": "温度", - "nav_motion": "运动", - "nav_ams": "AMS", - "nav_extras": "灯光 / 风扇", - "nav_console": "控制台", - "card_progress": "进度", - "card_temps": "温度", - "card_light_fan": "风扇", - "card_speed": "打印速度", - "card_cam": "相机", - "lbl_elapsed": "已用时间:", - "lbl_remaining": "剩余时间:", - "lbl_slicer_time": "切片预估:", - "lbl_layers": "层", - "lbl_zpos": "Z (mm)", - "speed_silent": "🐢 静音", - "speed_normal": "⚡ 标准", - "speed_sport": "🚀 运动", - "lbl_light": "💡 灯光", - "lbl_feed": "进料", - "lbl_unload": "退料", - "card_ace_dry": "ACE 烘干", + "ace_dry_auto_refill": "自动补料", + "ace_dry_chart": "历史 (温度/湿度)", + "ace_dry_current_temp": "温度", + "ace_dry_dialog_cancel": "取消", + "ace_dry_dialog_confirm": "确认", + "ace_dry_dialog_custom_name": "自定义名称", + "ace_dry_dialog_reset_default": "恢复默认", + "ace_dry_dialog_save_restart": "保存并重启", + "ace_dry_dialog_temp": "温度 (30-80°C)", + "ace_dry_dialog_time": "剩余时间 (h:m:s)", + "ace_dry_dialog_title": "烘干温度/时间设置", "ace_dry_dryer": "烘干机", + "ace_dry_duration": "时长 (分钟)", + "ace_dry_enable": "启用烘干", + "ace_dry_humidity": "湿度", + "ace_dry_preset_abs_asa": "ABS / ASA", + "ace_dry_preset_custom": "自定义", + "ace_dry_preset_pa_pc": "PA / PC", + "ace_dry_preset_petg": "PETG", + "ace_dry_preset_pla": "PLA", + "ace_dry_preset_pla_plus": "PLA+", + "ace_dry_preset_tpu": "TPU", + "ace_dry_start": "▶ 启动", "ace_dry_status_off": "状态: 关闭", "ace_dry_status_on": "状态: 运行中", "ace_dry_status_remaining": "剩余", - "ace_dry_humidity": "湿度", - "ace_dry_current_temp": "温度", - "ace_dry_chart": "历史 (温度/湿度)", - "ace_dry_temp": "温度 (°C)", - "ace_dry_duration": "时长 (分钟)", - "ace_dry_start": "▶ 启动", "ace_dry_stop": "■ 停止", - "ace_dry_auto_refill": "自动补料", - "ace_dry_enable": "启用烘干", + "ace_dry_temp": "温度 (°C)", "ace_dry_temp_line": "烘干温度", "ace_dry_time_line": "烘干时间", "ace_dry_ui_pending": "(仅 UI,后端稍后支持)", - "ace_dry_dialog_title": "烘干温度/时间设置", - "ace_dry_dialog_temp": "温度 (30-80°C)", - "ace_dry_dialog_time": "剩余时间 (h:m:s)", - "ace_dry_dialog_confirm": "确认", - "ace_dry_dialog_cancel": "取消", - "ace_dry_dialog_save_restart": "保存并重启", - "ace_dry_dialog_custom_name": "自定义名称", - "ace_dry_dialog_reset_default": "恢复默认", - "cam_placeholder": "📷 相机未启动", - "cam_stream_unavailable": "视频流不可用", + "add_printer": "添加打印机", + "ams_empty": "空", + "ams_no_data": "未收到 AMS 数据", + "apd_cancel": "取消", + "apd_confirm": "添加", + "apd_err_ip": "请输入 IP 地址", + "apd_fetching": "正在从打印机获取数据…", + "apd_lbl_ip": "打印机 IP", + "apd_lbl_name": "名称 (可选)", + "apd_placeholder_name": "例如 Kobra X 客厅", + "apd_success": "打印机已添加,Bridge 正在重启…", + "apd_title": "添加打印机", "btn_cam_start": "▶ 相机", + "btn_cam_start2": "▶ 启动", "btn_cam_stop": "◼ 相机", + "btn_cam_stop2": "◼ 停止", + "btn_cancel": "✕ 停止", + "btn_cancel_generic": "取消", + "btn_confirm_generic": "确认", + "btn_connect": "⚡ 连接", + "btn_delete": "删除", + "btn_disable_motors": "关闭电机", + "btn_disconnect": "✕ 断开", + "btn_home_all": "全部回零", + "btn_home_xy": "回零 XY", + "btn_home_z": "回零 Z", "btn_pause": "⏸ 暂停", "btn_resume": "▶ 继续", - "btn_cancel": "✕ 停止", - "label_nozzle": "喷嘴", + "cam_placeholder": "📷 相机未启动", + "cam_stream_unavailable": "视频流不可用", + "card_ace_dry": "ACE 烘干", + "card_ams": "耗材", + "card_cam": "相机", + "card_light_fan": "风扇", + "card_progress": "进度", + "card_speed": "打印速度", + "card_temps": "温度", + "confirm_cancel": "确定要取消打印吗?", + "fd_cancel": "取消", + "fd_no_matching_material": "无匹配材料", + "fd_no_slots_msg": "没有已装载的 AMS 槽位。{br}仍要开始打印吗?", + "fd_objects_hint": "跳过对象 (可选):", + "fd_objects_toggle": "跳过对象", + "fd_options_title": "选项", + "fd_print": "▶ 打印", + "fd_slot": "槽位", + "fd_slots_hint": "将 GCode 通道分配到 AMS 槽位:", + "fd_used": "已用", + "file_cancel_btn": "✕ 取消", + "file_ready_btn": "▶ 开始打印", + "file_slots_btn": "🎨 选择槽位", + "header_status_complete": "完成", + "header_status_error": "错误", + "header_status_printing": "打印中", + "header_status_standby": "就绪", + "hint_ip_no_port": "仅填写 IP,不要端口 (例如 192.168.1.102)", + "kobra_auto_leveling": "自动调平", + "kobra_busy": "忙碌", + "kobra_canceled": "已取消", + "kobra_checking": "检查中", + "kobra_failed": "错误", + "kobra_finished": "已完成", + "kobra_free": "就绪", + "kobra_init": "初始化中", + "kobra_offline": "离线", + "kobra_paused": "已暂停", + "kobra_pausing": "暂停中...", + "kobra_preheating": "预热中", + "kobra_printing": "打印中", + "kobra_resumed": "已恢复", + "kobra_resuming": "恢复中...", + "kobra_stoped": "已停止", + "kobra_stopping": "停止中...", + "kobra_updated": "更新中", "label_bed": "热床", "label_fan": "🌀 风扇", "label_light": "💡 灯光", - "label_on_off": "开 / 关", - "label_speed": "速度", - "panel_print_title": "打印控制", - "panel_print_btn_pause": "⏸ 暂停", - "panel_print_btn_resume": "▶ 继续", - "panel_print_btn_cancel": "✕ 取消", - "panel_print_temps_live": "温度 (实时)", - "label_set": "设置", + "label_nozzle": "喷嘴", "label_off": "关闭", - "panel_temps_nozzle": "喷嘴", - "panel_temps_bed": "热床", - "panel_temps_chart": "历史 (最近 60 次读数)", - "label_target_c": "目标:", - "panel_motion_xy": "XY 轴", - "panel_motion_z": "Z 轴", - "label_step": "步进:", - "btn_home_z": "回零 Z", - "btn_home_xy": "回零 XY", - "btn_home_all": "全部回零", - "btn_disable_motors": "关闭电机", - "panel_ams_title": "耗材", - "card_ams": "耗材", - "ams_no_data": "未收到 AMS 数据", + "label_on_off": "开 / 关", + "label_set": "设置", "label_slot": "槽位", - "ams_empty": "空", - "panel_extras_light": "灯光", - "panel_extras_fan": "风扇", - "panel_extras_camera": "相机", - "btn_cam_start2": "▶ 启动", - "btn_cam_stop2": "◼ 停止", - "panel_console_title": "事件日志", - "log_light_on": "灯光已开", - "log_light_off": "灯光已关", - "log_fan": "风扇 →", - "log_nozzle": "喷嘴 →", - "log_bed": "热床 →", + "label_speed": "速度", + "label_step": "步进:", + "label_target_c": "目标:", + "lbl_conn_error": "连接错误:", + "lbl_elapsed": "已用时间:", + "lbl_feed": "进料", + "lbl_layers": "层", + "lbl_light": "💡 灯光", + "lbl_remaining": "剩余时间:", + "lbl_slicer_time": "切片预估:", + "lbl_unload": "退料", + "lbl_zpos": "Z (mm)", + "log_auto": "⬇ 自动", "log_axis": "轴", - "log_home": "回零", - "log_home_all": "全部回零", + "log_bed": "热床 →", "log_cam_start": "相机已启动:", "log_cam_stop": "相机已停止", - "log_poll_error": "轮询错误:", + "log_clear": "✕ 清空", + "log_delete_failed": "删除失败", + "log_dir_all": "全部", + "log_dir_label": "方向:", + "log_dir_rx": "RX", + "log_dir_tx": "TX", + "log_download": "⬇ 下载", "log_error": "错误:", - "confirm_cancel": "确定要取消打印吗?", - "settings_title": "设置", - "settings_connection": "连接", - "settings_print": "打印设置", - "settings_poll": "轮询间隔(秒)", + "log_fan": "风扇 →", + "log_filter_placeholder": "筛选…", + "log_home": "回零", + "log_home_all": "全部回零", + "log_light_off": "灯光已关", + "log_light_on": "灯光已开", + "log_lvl_err": "⛔ 错误", + "log_lvl_label": "级别:", + "log_lvl_warn": "⚠ 警告", + "log_nozzle": "喷嘴 →", + "log_poll_error": "轮询错误:", + "log_print_action": "打印:", + "log_print_start": "打印开始:", + "log_topic_ams": "AMS", + "log_topic_info": "信息", + "log_topic_label": "主题:", + "log_topic_print": "打印", + "log_topic_status": "状态", + "nav_ams": "AMS", + "nav_browser": "浏览器", + "nav_console": "控制台", + "nav_dashboard": "仪表盘", + "nav_extras": "灯光 / 风扇", + "nav_motion": "运动", + "nav_print": "打印", + "nav_printers": "打印机", "nav_settings": "设置", - "settings_cat_display": "外观", - "settings_cat_filament": "耗材", - "settings_cat_language": "语言", - "settings_cat_theme": "切换浅色 / 深色", - "settings_filament_mapping": "耗材配置映射(每槽位)", - "settings_filament_mapping_save": "保存映射", - "settings_visible_vendors": "可见厂商(配置下拉框)", - "settings_visible_vendors_hint": "仅这些厂商会出现在槽位配置下拉框中。未选择 = 显示全部。“Generic”和您自己的配置始终可见。", - "settings_visible_vendors_save": "保存选择", - "progress_action_print": "打印", - "progress_action_slots": "分配槽位", - "progress_action_clear": "清除", - "settings_version": "版本", - "settings_save": "保存并重启", - "settings_printer_name": "打印机名称", - "settings_printer_ip": "打印机 IP", - "settings_mqtt_port": "MQTT 端口", - "settings_username": "MQTT 用户名", - "settings_password": "MQTT 密码", - "settings_device_id": "设备 ID", - "settings_mode_id": "模式 ID", - "hint_ip_no_port": "仅填写 IP,不要端口 (例如 192.168.1.102)", - "settings_default_slot": "默认槽位 (单色)", - "settings_slot_auto": "自动 (所有已装载槽位)", - "settings_auto_leveling": "打印前自动调平", - "settings_camera_on_print": "打印开始时开启相机", - "settings_web_upload_warning": "打印网页上传文件时显示警告", - "update_check": "检查更新", - "update_checking": "检查中...", - "update_available": "可用", - "update_none": "已是最新版本", - "update_apply": "立即安装", - "update_applying": "下载中...", - "update_restarting": "重启中...", - "update_error": "错误", - "btn_connect": "⚡ 连接", - "btn_disconnect": "✕ 断开", - "lbl_conn_error": "连接错误:", - "slot_edit_title": "编辑槽位", - "slot_edit_color": "颜色", - "slot_edit_material": "材料", - "slot_edit_load": "⬇ 进料", - "slot_edit_unload": "⬆ 退料", - "slot_edit_save": "💾 保存", - "slot_edit_custom": "例如 PLA, PETG, ABS…", - "slot_edit_ok": "AMS 槽位", - "slot_edit_profile": "OrcaSlicer 配置", - "slot_edit_profile_hint": "在 OrcaSlicer 同步时发送具体品牌,而不仅仅是“Generic”", - "slot_edit_profile_default": "— 通用 (默认) —", - "orca_profile_section": "OrcaSlicer 配置", + "nav_temps": "温度", + "orca_profile_done": "已导入", + "orca_profile_dropmsg": "拖到此处或点击", + "orca_profile_help_html": "上传 OrcaSlicer 耗材文件夹的 ZIP 或单个 .json 文件。
在 OrcaSlicer 中: Help → Show Configuration Folder → user/<id>/filament/", "orca_profile_hint": "导入你自己的 OrcaSlicer 耗材配置(在 Help → Show Configuration Folder 打开用户目录)", "orca_profile_import_btn": "导入配置", "orca_profile_import_link": "★ 导入自己的配置…", "orca_profile_import_title": "导入你的 OrcaSlicer 配置", - "orca_profile_help_html": "上传 OrcaSlicer 耗材文件夹的 ZIP 或单个 .json 文件。
在 OrcaSlicer 中: Help → Show Configuration Folder → user/<id>/filament/", - "orca_profile_dropmsg": "拖到此处或点击", "orca_profile_list_label": "已导入", - "orca_profile_user_label": "自己的配置", - "orca_profile_user_empty": "– 无 –", - "orca_profile_uploading": "上传中…", - "orca_profile_done": "已导入", + "orca_profile_section": "OrcaSlicer 配置", "orca_profile_skipped": "跳过", - "log_dir_all": "全部", - "log_lvl_label": "级别:", - "file_ready_btn": "▶ 开始打印", - "file_slots_btn": "🎨 选择槽位", - "file_cancel_btn": "✕ 取消", - "nav_printers": "打印机", - "skip_title": "✂ 跳过对象", - "skip_hint": "取消勾选不想继续打印的对象:", - "skip_btn_label": "对象", - "skip_no_objects": "此打印任务没有对象。", + "orca_profile_uploading": "上传中…", + "orca_profile_user_empty": "– 无 –", + "orca_profile_user_label": "自己的配置", + "panel_ams_title": "耗材", + "panel_browser_title": "文件浏览器", + "panel_console_title": "事件日志", + "panel_extras_camera": "相机", + "panel_extras_fan": "风扇", + "panel_extras_light": "灯光", + "panel_motion_xy": "XY 轴", + "panel_motion_z": "Z 轴", + "panel_print_btn_cancel": "✕ 取消", + "panel_print_btn_pause": "⏸ 暂停", + "panel_print_btn_resume": "▶ 继续", + "panel_print_temps_live": "温度 (实时)", + "panel_print_title": "打印控制", + "panel_temps_bed": "热床", + "panel_temps_chart": "历史 (最近 60 次读数)", + "panel_temps_nozzle": "喷嘴", + "print_auto_leveling": "本次打印自动调平", + "printers_active": "● 活动", + "printers_current": "当前打印机", + "printers_empty_hint": "尚未设置打印机。", + "printers_loading": "加载中…", + "printers_none": "未配置打印机。", + "printers_remove": "移除打印机", + "printers_remove_confirm": "移除打印机 \"{name}\"? Bridge 将重启。", + "printers_switch": "切换 →", + "progress_action_clear": "清除", + "progress_action_print": "打印", + "progress_action_slots": "分配槽位", + "settings_auto_leveling": "打印前自动调平", + "settings_btn_tooltip": "设置", + "settings_camera_on_print": "打印开始时开启相机", + "settings_cat_display": "外观", + "settings_cat_filament": "耗材", + "settings_cat_language": "语言", + "settings_cat_notifications": "通知", + "settings_cat_system": "系统", + "settings_cat_theme": "切换浅色 / 深色", + "settings_connection": "连接", + "settings_default_slot": "默认槽位 (单色)", + "settings_device_id": "设备 ID", + "settings_device_id_hint": "32 个十六进制字符", + "settings_device_id_placeholder": "32 个十六进制字符", + "settings_filament_mapping": "耗材配置映射(每槽位)", + "settings_filament_mapping_save": "保存映射", + "settings_file_ready_banner": "打印栏", + "settings_file_ready_dialog": "打印对话框", + "settings_file_ready_mode": "上传后:开始打印行为", + "settings_language": "语言", + "settings_mode_id": "模式 ID", + "settings_mode_id_placeholder": "20030", + "settings_mqtt_port": "MQTT 端口", + "settings_mqtt_username_placeholder": "userXXXXXXXX", + "settings_notif_add": "添加通知", + "settings_notif_empty": "未配置任何通知。", + "settings_notif_ev_cancelled": "已取消", + "settings_notif_ev_failed": "失败", + "settings_notif_ev_finished": "已完成", + "settings_notif_ev_paused": "已暂停", + "settings_notif_ev_progress": "进度", + "settings_notif_ev_started": "已开始", + "settings_notif_interval_lbl": "重复间隔", + "settings_notif_layers_unit": "层", + "settings_notif_min_unit": "分钟", + "settings_notif_send_image": "图像", + "settings_notif_test": "测试", + "settings_notif_test_fail": "失败", + "settings_notif_test_ok": "已发送", + "settings_notif_zero_off": "(0 = 关闭)", + "settings_notifications": "通知", + "settings_notifications_hint": "通过 Apprise URL 发送通知 (discord://, telegram://, gotify://, slack://, …)", + "settings_orca_profiles_import": "导入配置文件", + "settings_orca_profiles_label": "OrcaSlicer 配置文件", + "settings_password": "MQTT 密码", + "settings_poll": "轮询间隔(秒)", + "settings_print": "打印设置", + "settings_printer_ip": "打印机 IP", + "settings_printer_name": "打印机名称", + "settings_printer_name_placeholder": "例如 Kobra X 左", + "settings_save": "保存并重启", + "settings_slot_auto": "自动 (所有已装载槽位)", + "settings_theme_toggle": "切换浅色 / 深色", + "settings_title": "设置", + "settings_username": "MQTT 用户名", + "settings_version": "版本", + "settings_visible_vendors": "可见厂商(配置下拉框)", + "settings_visible_vendors_hint": "仅这些厂商会出现在槽位配置下拉框中。未选择 = 显示全部。“Generic”和您自己的配置始终可见。", + "settings_visible_vendors_save": "保存选择", + "settings_web_upload_warning": "打印网页上传文件时显示警告", + "sf_all": "全部", + "sf_err": "✗ 失败", + "sf_new": "新", + "sf_ok": "✓ 已完成", "skip_already": "已跳过", + "skip_btn_label": "对象", + "skip_cancel": "取消", + "skip_confirm": "跳过", + "skip_confirm_btn": "跳过", + "skip_hint": "取消勾选不想继续打印的对象:", + "skip_no_objects": "此打印任务没有对象。", "skip_select_at_least_one": "请至少选择一个对象。", "skip_sending": "发送中 …", "skip_success": "对象将被跳过。", - "fd_objects_hint": "跳过对象 (可选):", - "fd_objects_toggle": "跳过对象", - "fd_slots_hint": "将 GCode 通道分配到 AMS 槽位:", - "fd_cancel": "取消", - "fd_print": "▶ 打印", - "fd_no_slots_msg": "没有已装载的 AMS 槽位。{br}仍要开始打印吗?", - "fd_slot": "槽位", - "fd_no_matching_material": "无匹配材料", - "fd_used": "已用", - "add_printer": "添加打印机", - "apd_lbl_ip": "打印机 IP", - "apd_lbl_name": "名称 (可选)", - "apd_placeholder_name": "例如 Kobra X 客厅", - "apd_cancel": "取消", - "apd_confirm": "添加", - "apd_fetching": "正在从打印机获取数据…", - "apd_success": "打印机已添加,Bridge 正在重启…", - "apd_err_ip": "请输入 IP 地址", - "printers_remove": "移除打印机", - "printers_remove_confirm": "移除打印机 \"{name}\"? Bridge 将重启。", - "printers_active": "● 活动", - "printers_switch": "切换 →", - "printers_current": "当前打印机", - "printers_loading": "加载中…", - "printers_none": "未配置打印机。", - "printers_empty_hint": "尚未设置打印机。", - "nav_browser": "浏览器", - "panel_browser_title": "文件浏览器", - "store_search_placeholder": "🔍 搜索…", - "store_empty": "尚未上传文件。", - "store_refresh": "↻ 刷新", - "store_print": "▶ 打印", - "store_download": "⬇ 下载", - "store_delete_confirm": "删除文件?", - "store_print_confirm": "打印文件?", - "store_web_verify_title": "验证文件", - "store_web_verify_msg": "请确认此文件是为 Anycubic Kobra X 创建的。", - "store_web_verify_confirm": "确认", - "store_web_verify_abort": "取消", - "store_no_results": "未找到文件。", - "store_never": "从未打印", - "store_estimate": "估算", - "store_upload_label_prefix": "将 GCode 拖到这里或 ", - "store_upload_label_browse": "浏览", - "store_upload_busy": "⏳ 上传中…", - "store_upload_success": "✓ {file}", - "store_upload_error": "✗ {error}", - "store_upload_only_gcode": "✗ 仅允许 GCode 文件 (.gcode, .3mf, .bgcode)", - "sf_all": "全部", - "sf_ok": "✓ 已完成", - "sf_err": "✗ 失败", - "sf_new": "新", + "skip_title": "✂ 跳过对象", + "slot_edit_color": "颜色", + "slot_edit_custom": "例如 PLA, PETG, ABS…", + "slot_edit_load": "⬇ 进料", + "slot_edit_material": "材料", + "slot_edit_ok": "AMS 槽位", + "slot_edit_profile": "OrcaSlicer 配置", + "slot_edit_profile_default": "— 通用 (默认) —", + "slot_edit_profile_hint": "在 OrcaSlicer 同步时发送具体品牌,而不仅仅是“Generic”", + "slot_edit_save": "💾 保存", + "slot_edit_title": "编辑槽位", + "slot_edit_unload": "⬆ 退料", + "speed_normal": "⚡ 标准", + "speed_silent": "🐢 静音", + "speed_sport": "🚀 运动", "ss_date": "↓ 日期", - "ss_name": "A–Z 名称", "ss_dur": "⏱ 打印时间", - "ace_dry_preset_pla": "PLA", - "ace_dry_preset_pla_plus": "PLA+", - "ace_dry_preset_petg": "PETG", - "ace_dry_preset_tpu": "TPU", - "ace_dry_preset_abs_asa": "ABS / ASA", - "ace_dry_preset_pa_pc": "PA / PC", - "ace_dry_preset_custom": "自定义", - "fd_options_title": "选项", - "print_auto_leveling": "本次打印自动调平", - "settings_file_ready_mode": "开始打印对话框", - "settings_file_ready_banner": "打印栏", - "settings_file_ready_dialog": "打印对话框", - "log_dir_rx": "RX", - "log_dir_tx": "TX", - "log_dir_label": "方向:", - "log_lvl_err": "⛔ 错误", - "log_lvl_warn": "⚠ 警告", - "log_topic_label": "主题:", - "log_topic_ams": "AMS", - "log_topic_print": "打印", - "log_topic_info": "信息", - "log_topic_status": "状态", - "log_download": "⬇ 下载", - "log_auto": "⬇ 自动", - "log_clear": "✕ 清空", - "log_filter_placeholder": "筛选…", - "skip_cancel": "取消", - "skip_confirm": "跳过", - "settings_cat_connection": "连接", - "settings_cat_printer": "打印机", - "settings_cat_notifications": "通知", - "settings_cat_system": "系统", - "settings_notifications": "通知", - "settings_notifications_hint": "通过 Apprise URL 发送通知 (discord://, telegram://, gotify://, slack://, …)", - "settings_notif_add": "添加通知", - "settings_notif_empty": "未配置任何通知。", - "settings_notif_test": "测试", - "settings_notif_test_ok": "已发送", - "settings_notif_test_fail": "失败", - "settings_notif_ev_finished": "已完成", - "settings_notif_ev_failed": "失败", - "settings_notif_ev_cancelled": "已取消", - "settings_notif_ev_paused": "已暂停", - "settings_notif_ev_started": "已开始", - "settings_notif_ev_progress": "进度", - "settings_notif_interval_lbl": "重复间隔", - "settings_notif_min_unit": "分钟", - "settings_notif_layers_unit": "层", - "settings_notif_zero_off": "(0 = 关闭)", - "settings_notif_send_image": "图像", - "settings_btn_tooltip": "设置", - "settings_printer_name_placeholder": "例如 Kobra X 左", - "settings_device_id_placeholder": "32 个十六进制字符", - "settings_mqtt_username_placeholder": "userXXXXXXXX", - "settings_device_id_hint": "32 个十六进制字符", - "settings_mode_id_placeholder": "20030", - "settings_language": "语言", - "settings_theme_toggle": "切换浅色 / 深色", - "settings_orca_profiles_label": "OrcaSlicer 配置文件", - "settings_orca_profiles_import": "导入配置文件", - "skip_confirm_btn": "跳过", - "btn_delete": "删除", - "log_print_start": "打印开始:", - "log_print_action": "打印:", - "log_delete_failed": "删除失败" + "ss_name": "A–Z 名称", + "store_delete_confirm": "删除文件?", + "store_download": "⬇ 下载", + "store_empty": "尚未上传文件。", + "store_estimate": "估算", + "store_never": "从未打印", + "store_no_results": "未找到文件。", + "store_print": "▶ 打印", + "store_print_confirm": "打印文件?", + "store_refresh": "↻ 刷新", + "store_search_placeholder": "🔍 搜索…", + "store_upload_busy": "⏳ 上传中…", + "store_upload_error": "✗ {error}", + "store_upload_label_browse": "浏览", + "store_upload_label_prefix": "将 GCode 拖到这里或 ", + "store_upload_only_gcode": "✗ 仅允许 GCode 文件 (.gcode, .3mf, .bgcode)", + "store_upload_success": "✓ {file}", + "store_web_verify_abort": "取消", + "store_web_verify_confirm": "确认", + "store_web_verify_msg": "请验证此文件是为Anycubic Kobra X创建的。", + "store_web_verify_title": "验证文件", + "update_apply": "立即安装", + "update_applying": "下载中...", + "update_available": "可用", + "update_check": "检查更新", + "update_checking": "检查中...", + "update_error": "错误", + "update_none": "已是最新版本", + "update_restarting": "重启中..." }