forked from viewit/KX-Bridge-Release
2573 lines
89 KiB
HTML
2573 lines
89 KiB
HTML
<!doctype html>
|
||
<html lang="de" data-theme="dark">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||
<title>KX-Bridge</title>
|
||
<link rel="stylesheet" href="/kx/ui/style.css" />
|
||
</head>
|
||
<body>
|
||
<div
|
||
id="conn-error-banner"
|
||
style="
|
||
display: none;
|
||
background: #c0392b;
|
||
color: #fff;
|
||
padding: 10px 18px;
|
||
font-size: 14px;
|
||
text-align: center;
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 999;
|
||
"
|
||
></div>
|
||
<div
|
||
id="file-ready-banner"
|
||
style="
|
||
display: none;
|
||
background: #1a6e3c;
|
||
color: #fff;
|
||
padding: 10px 18px;
|
||
font-size: 14px;
|
||
text-align: center;
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 998;
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 12px;
|
||
flex-wrap: wrap;
|
||
"
|
||
>
|
||
<span>📄 <span id="file-ready-name"></span></span>
|
||
<button
|
||
id="file-ready-btn"
|
||
onclick="startReadyFile()"
|
||
style="
|
||
padding: 5px 16px;
|
||
background: #fff;
|
||
color: #1a6e3c;
|
||
border: none;
|
||
border-radius: 6px;
|
||
font-weight: 700;
|
||
cursor: pointer;
|
||
font-size: 13px;
|
||
"
|
||
></button>
|
||
<button
|
||
id="file-slots-btn"
|
||
onclick="startReadyFileWithSlots()"
|
||
style="
|
||
padding: 5px 16px;
|
||
background: rgba(255, 255, 255, 0.15);
|
||
color: #fff;
|
||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||
border-radius: 6px;
|
||
font-weight: 700;
|
||
cursor: pointer;
|
||
font-size: 13px;
|
||
"
|
||
></button>
|
||
<button
|
||
id="file-cancel-btn"
|
||
onclick="cancelReadyFile()"
|
||
style="
|
||
padding: 5px 16px;
|
||
background: rgba(255, 255, 255, 0.15);
|
||
color: #fff;
|
||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||
border-radius: 6px;
|
||
font-weight: 700;
|
||
cursor: pointer;
|
||
font-size: 13px;
|
||
"
|
||
></button>
|
||
</div>
|
||
|
||
<header>
|
||
<div class="logo">⬡ KX-Bridge</div>
|
||
<div style="flex: 1"></div>
|
||
<div id="printer-dropdown-wrap" style="display: none; position: relative">
|
||
<button
|
||
id="printer-dropdown-btn"
|
||
onclick="togglePrinterDropdown()"
|
||
style="
|
||
background: var(--raised);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
padding: 4px 10px;
|
||
color: var(--txt);
|
||
cursor: pointer;
|
||
font-size: 13px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
"
|
||
>
|
||
<span id="h-pname">Anycubic Kobra X</span><span style="opacity: 0.5">▾</span>
|
||
</button>
|
||
<div
|
||
id="printer-dropdown-menu"
|
||
style="
|
||
display: none;
|
||
position: absolute;
|
||
top: calc(100% + 4px);
|
||
right: 0;
|
||
background: var(--card);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
min-width: 200px;
|
||
z-index: 200;
|
||
box-shadow: 0 4px 16px #0006;
|
||
overflow: hidden;
|
||
"
|
||
></div>
|
||
</div>
|
||
<div id="h-pname-single" class="hname">Anycubic Kobra X</div>
|
||
<span id="h-version" style="font-size: 11px; opacity: 0.5; margin-left: 6px"></span>
|
||
<div class="hbadge" id="h-badge">
|
||
<span class="dot"></span><span id="h-state">Standby</span>
|
||
</div>
|
||
<button class="theme-btn" onclick="toggleTheme()">☀ / ☾</button>
|
||
<div style="display: flex; align-items: center; gap: 6px">
|
||
<span aria-hidden="true" style="font-size: 15px; line-height: 1; opacity: 0.85">🌐</span>
|
||
<select
|
||
class="theme-btn"
|
||
id="lang-select"
|
||
onchange="setLanguageFromSelect()"
|
||
style="padding: 6px 10px"
|
||
>
|
||
<option value="de">Deutsch</option>
|
||
<option value="en">English</option>
|
||
<option value="es">Espanol</option>
|
||
<option value="fr">Français</option>
|
||
<option value="it">Italiano</option>
|
||
<option value="zh-cn">中文(简体)</option>
|
||
</select>
|
||
</div>
|
||
<button
|
||
class="theme-btn"
|
||
onclick="showPanel('settings')"
|
||
id="settings-btn"
|
||
title="Einstellungen"
|
||
>
|
||
⚙
|
||
</button>
|
||
<button class="conn-btn disconnected" id="conn-btn" onclick="toggleConnection()">
|
||
⚡ Verbinden
|
||
</button>
|
||
</header>
|
||
|
||
<!-- ═══ AMS SLOT EDIT DIALOG ═══ -->
|
||
<div
|
||
class="modal-overlay"
|
||
id="slot-edit-modal"
|
||
onclick="if (event.target === this) closeSlotEdit();"
|
||
>
|
||
<div class="modal-box" style="max-width: 340px">
|
||
<div
|
||
style="
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 16px;
|
||
"
|
||
>
|
||
<span class="modal-title" id="slot-edit-title"></span>
|
||
<button
|
||
onclick="closeSlotEdit()"
|
||
style="
|
||
background: none;
|
||
border: none;
|
||
color: var(--txt2);
|
||
font-size: 20px;
|
||
cursor: pointer;
|
||
line-height: 1;
|
||
"
|
||
>
|
||
✕
|
||
</button>
|
||
</div>
|
||
<div style="display: flex; align-items: center; gap: 16px; margin-bottom: 20px">
|
||
<div
|
||
id="slot-edit-preview"
|
||
style="
|
||
width: 56px;
|
||
height: 56px;
|
||
border-radius: 50%;
|
||
border: 3px solid rgba(255, 255, 255, 0.2);
|
||
flex-shrink: 0;
|
||
"
|
||
></div>
|
||
<div style="flex: 1">
|
||
<div
|
||
style="font-size: 11px; color: var(--txt2); margin-bottom: 4px"
|
||
id="lbl-slot-color"
|
||
></div>
|
||
<input
|
||
type="color"
|
||
id="slot-edit-color"
|
||
oninput="document.getElementById('slot-edit-preview').style.background = this.value"
|
||
style="
|
||
width: 100%;
|
||
height: 36px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
background: var(--raised);
|
||
cursor: pointer;
|
||
padding: 2px;
|
||
"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div style="margin-bottom: 20px">
|
||
<div
|
||
style="font-size: 11px; color: var(--txt2); margin-bottom: 6px"
|
||
id="lbl-slot-material"
|
||
></div>
|
||
<div style="display: flex; flex-wrap: wrap; gap: 6px" id="slot-mat-btns"></div>
|
||
<input
|
||
type="text"
|
||
id="slot-edit-mat"
|
||
oninput="highlightMatBtn(this.value)"
|
||
style="
|
||
margin-top: 8px;
|
||
width: 100%;
|
||
padding: 6px 10px;
|
||
background: var(--raised);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
color: var(--txt);
|
||
font-size: 13px;
|
||
box-sizing: border-box;
|
||
"
|
||
/>
|
||
</div>
|
||
<!-- Orca-Filament-Profil-Override (für AMS-Sync) -->
|
||
<div style="margin-bottom: 20px">
|
||
<div
|
||
style="font-size: 11px; color: var(--txt2); margin-bottom: 6px"
|
||
id="lbl-slot-profile"
|
||
></div>
|
||
<select
|
||
id="slot-edit-profile"
|
||
style="
|
||
width: 100%;
|
||
padding: 6px 10px;
|
||
background: var(--raised);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
color: var(--txt);
|
||
font-size: 13px;
|
||
box-sizing: border-box;
|
||
"
|
||
>
|
||
<option value="" id="slot-profile-default-opt"></option>
|
||
</select>
|
||
<div
|
||
style="font-size: 11px; color: var(--txt2); margin-top: 4px"
|
||
id="slot-profile-hint"
|
||
></div>
|
||
<a
|
||
href="#"
|
||
onclick="
|
||
event.preventDefault();
|
||
openProfileImport();
|
||
"
|
||
style="
|
||
display: inline-block;
|
||
margin-top: 6px;
|
||
font-size: 11px;
|
||
color: var(--accent);
|
||
text-decoration: none;
|
||
"
|
||
id="lbl-slot-profile-import"
|
||
>★ Eigene Profile importieren…</a
|
||
>
|
||
</div>
|
||
<button
|
||
class="btn"
|
||
id="btn-slot-edit-feed"
|
||
style="width: 100%; margin-bottom: 8px"
|
||
onclick="slotEditFeed()"
|
||
></button>
|
||
<button class="modal-save" id="btn-slot-edit-save" onclick="saveSlotEdit()"></button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══ ORCA-PROFILE-IMPORT-DIALOG (Issue #41) ═══ -->
|
||
<div
|
||
class="modal-overlay"
|
||
id="profile-import-modal"
|
||
onclick="if (event.target === this) closeProfileImport();"
|
||
>
|
||
<div class="modal-box" style="max-width: 480px">
|
||
<div
|
||
style="
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 16px;
|
||
"
|
||
>
|
||
<div style="font-size: 16px; font-weight: 600" id="profile-import-title">
|
||
Eigene OrcaSlicer-Profile importieren
|
||
</div>
|
||
<button
|
||
onclick="closeProfileImport()"
|
||
style="
|
||
background: none;
|
||
border: none;
|
||
color: var(--txt2);
|
||
font-size: 20px;
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
<div
|
||
style="font-size: 12px; color: var(--txt2); margin-bottom: 12px; line-height: 1.5"
|
||
id="profile-import-help"
|
||
>
|
||
Lade ein <b>ZIP</b> deines OrcaSlicer-Filament-Ordners oder einzelne <b>.json</b>-Files
|
||
hoch.<br />
|
||
In OrcaSlicer: <i>Help → Show Configuration Folder → user/<id>/filament/</i>
|
||
</div>
|
||
<div
|
||
id="profile-import-drop"
|
||
style="
|
||
border: 2px dashed var(--border);
|
||
border-radius: 8px;
|
||
padding: 24px;
|
||
text-align: center;
|
||
cursor: pointer;
|
||
margin-bottom: 12px;
|
||
"
|
||
ondragover="
|
||
event.preventDefault();
|
||
this.style.borderColor = 'var(--accent)';
|
||
"
|
||
ondragleave="this.style.borderColor = 'var(--border)'"
|
||
ondrop="
|
||
event.preventDefault();
|
||
this.style.borderColor = 'var(--border)';
|
||
doProfileImportUpload(event.dataTransfer.files);
|
||
"
|
||
onclick="document.getElementById('profile-import-file').click()"
|
||
>
|
||
<div style="font-size: 32px; margin-bottom: 8px">⬆</div>
|
||
<div style="font-size: 13px; color: var(--txt2)" id="profile-import-dropmsg">
|
||
Hierher ziehen oder klicken
|
||
</div>
|
||
<input
|
||
type="file"
|
||
id="profile-import-file"
|
||
accept=".zip,.json"
|
||
multiple
|
||
style="display: none"
|
||
onchange="
|
||
doProfileImportUpload(this.files);
|
||
this.value = '';
|
||
"
|
||
/>
|
||
</div>
|
||
<div
|
||
id="profile-import-status"
|
||
style="font-size: 12px; margin-bottom: 12px; min-height: 18px"
|
||
></div>
|
||
<div
|
||
style="font-size: 11px; color: var(--txt2); margin-bottom: 6px"
|
||
id="profile-import-list-label"
|
||
>
|
||
Aktuell importiert
|
||
</div>
|
||
<div
|
||
id="profile-import-list"
|
||
style="max-height: 240px; overflow-y: auto; font-size: 12px"
|
||
></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="layout">
|
||
<nav class="sidebar">
|
||
<button class="nav-btn active" onclick="showPanel('dashboard')" id="nb-dashboard">
|
||
<span class="nav-icon">⊞</span><span class="nav-text">Dashboard</span>
|
||
</button>
|
||
<button
|
||
class="nav-btn"
|
||
onclick="
|
||
showPanel('printers');
|
||
loadPrinterTab();
|
||
"
|
||
id="nb-printers"
|
||
>
|
||
<span class="nav-icon">🖨</span><span class="nav-text">Drucker</span>
|
||
</button>
|
||
<button
|
||
class="nav-btn"
|
||
onclick="
|
||
showPanel('store');
|
||
loadStore();
|
||
"
|
||
id="nb-store"
|
||
>
|
||
<span class="nav-icon">🗂</span><span class="nav-text">Browser</span>
|
||
</button>
|
||
<button
|
||
class="nav-btn"
|
||
onclick="
|
||
showPanel('console');
|
||
clearLogBadge();
|
||
"
|
||
id="nb-console"
|
||
>
|
||
<span class="nav-icon">≡</span><span class="nav-text">Konsole</span
|
||
><span
|
||
id="log-badge"
|
||
style="
|
||
display: none;
|
||
margin-left: 4px;
|
||
background: var(--err);
|
||
color: #fff;
|
||
border-radius: 10px;
|
||
font-size: 10px;
|
||
padding: 1px 5px;
|
||
font-weight: 700;
|
||
"
|
||
></span>
|
||
</button>
|
||
<button class="nav-btn" onclick="showPanel('settings')" id="nb-settings">
|
||
<span class="nav-icon">⚙</span
|
||
><span class="nav-text" id="nav-settings">Einstellungen</span>
|
||
</button>
|
||
</nav>
|
||
|
||
<main>
|
||
<!-- ═══ DASHBOARD ═══ -->
|
||
<div class="panel active" id="panel-dashboard">
|
||
<div class="grid">
|
||
<!-- Kamera -->
|
||
<div class="card" style="grid-column: 1/-1">
|
||
<div
|
||
style="
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 10px;
|
||
"
|
||
>
|
||
<div class="card-title" style="margin-bottom: 0">
|
||
<span>📷</span> <span id="d-card-cam">Kamera</span>
|
||
</div>
|
||
<div style="display: flex; align-items: center; gap: 10px">
|
||
<span id="d-lbl-light" style="font-size: 12px; color: var(--txt2)">💡 Licht</span>
|
||
<label class="toggle">
|
||
<input type="checkbox" id="d-light-toggle" onchange="setLight()" />
|
||
<span class="toggle-track"></span>
|
||
<span class="toggle-thumb"></span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div class="cam-wrap" id="cam-wrap">
|
||
<div class="cam-placeholder" id="cam-placeholder">
|
||
<span id="cam-placeholder-txt">📷 Kamera nicht gestartet</span>
|
||
</div>
|
||
<div class="cam-spinner" id="cam-spinner"></div>
|
||
<img id="cam-img" style="display: none; width: 100%; height: auto" alt="Kamera" />
|
||
<div class="cam-overlay" id="cam-overlay" style="display: none">
|
||
<div style="font-size: 12px; color: #fff" id="cam-fname"></div>
|
||
</div>
|
||
<button class="cam-toggle" onclick="toggleCam()" id="cam-toggle-btn">
|
||
▶ Kamera
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Fortschritt -->
|
||
<div class="card" style="grid-column: 1/-1">
|
||
<div class="card-title">
|
||
<span>◉</span> <span id="d-card-progress">Fortschritt</span>
|
||
</div>
|
||
<img
|
||
id="d-thumbnail"
|
||
src=""
|
||
alt=""
|
||
style="
|
||
display: none;
|
||
width: 100%;
|
||
max-height: 160px;
|
||
object-fit: contain;
|
||
border-radius: 8px;
|
||
background: #111;
|
||
margin-bottom: 10px;
|
||
"
|
||
/>
|
||
<div class="pct-big"><span id="d-pct">0</span><small>%</small></div>
|
||
<div style="display: flex; align-items: center; gap: 10px; margin: 8px 0">
|
||
<div class="progress-bar" style="flex: 1; margin: 0">
|
||
<div class="progress-fill" id="d-pbar" style="width: 0%"></div>
|
||
</div>
|
||
<div style="display: flex; flex-direction: column; gap: 4px; flex-shrink: 0">
|
||
<div
|
||
class="time-block"
|
||
style="padding: 6px 10px; min-width: 72px; text-align: center"
|
||
>
|
||
<div class="time-label" id="d-lbl-layers"></div>
|
||
<div class="time-val" style="font-size: 16px" id="d-layers">–</div>
|
||
</div>
|
||
<div
|
||
class="time-block"
|
||
style="padding: 4px 10px; min-width: 72px; text-align: center"
|
||
>
|
||
<div class="time-label" id="d-lbl-zpos">Z</div>
|
||
<div class="time-val" style="font-size: 13px" id="d-zpos">–</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="time-grid">
|
||
<div class="time-block">
|
||
<div class="time-label" id="d-lbl-elapsed"></div>
|
||
<div class="time-val" id="d-elapsed">–</div>
|
||
</div>
|
||
<div class="time-block" id="d-slicer-row" style="display: none">
|
||
<div class="time-label" id="d-slicer-label"></div>
|
||
<div class="time-val" id="d-slicer-time">–</div>
|
||
</div>
|
||
<div class="time-block" style="color: var(--acc)">
|
||
<div class="time-label" id="d-lbl-remain"></div>
|
||
<div class="time-val" id="d-remain" style="color: var(--acc)">–</div>
|
||
</div>
|
||
</div>
|
||
<div class="fname" id="d-fname" title="" style="margin-top: 6px">–</div>
|
||
<div class="ctrl-btns" id="d-ctrl-btns" style="margin-top: 12px; display: none">
|
||
<button class="btn btn-pause btn-sm" id="d-btn-pause" onclick="togglePauseResume()">
|
||
⏸ Pause
|
||
</button>
|
||
<button
|
||
class="btn btn-skip btn-sm"
|
||
id="d-btn-skip"
|
||
onclick="openSkipDialog()"
|
||
style="display: none"
|
||
>
|
||
✂ <span id="d-btn-skip-label">Objekte</span>
|
||
</button>
|
||
<button class="btn btn-cancel btn-sm" id="d-btn-cancel" onclick="confirmCancel()">
|
||
✕ Stopp
|
||
</button>
|
||
</div>
|
||
<!-- Aktionen für eine geladene, aber nicht laufende Datei (Issue #55) -->
|
||
<div class="ctrl-btns" id="d-idle-btns" style="margin-top: 12px; display: none">
|
||
<button class="btn btn-accent btn-sm" id="d-idle-print" onclick="startIdleFile()">
|
||
▶ <span id="d-idle-print-lbl">Drucken</span>
|
||
</button>
|
||
<button
|
||
class="btn btn-sm"
|
||
id="d-idle-slots"
|
||
onclick="startIdleFileWithSlots()"
|
||
style="background: var(--raised); color: var(--txt)"
|
||
>
|
||
⚙ <span id="d-idle-slots-lbl">Slots zuordnen</span>
|
||
</button>
|
||
<button class="btn btn-cancel btn-sm" id="d-idle-clear" onclick="clearIdleFile()">
|
||
✕ <span id="d-idle-clear-lbl">Leeren</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Temperatursteuerung + Verlauf -->
|
||
<div class="card" style="grid-column: 1/-1">
|
||
<div class="card-title">
|
||
<span>⊙</span> <span id="d-card-temps">Temperaturen</span>
|
||
</div>
|
||
<div class="temp-card-inner">
|
||
<div class="temp-block">
|
||
<div class="temp-label" id="d-lbl-nozzle">Nozzle</div>
|
||
<div class="temp-row">
|
||
<div class="temp-val" id="d-nt">–</div>
|
||
<div class="temp-unit">°C</div>
|
||
</div>
|
||
<div class="temp-target">→ <span id="d-nt-t">0</span>°C</div>
|
||
<div class="progress-bar" style="margin: 8px 0 0">
|
||
<div
|
||
class="progress-fill"
|
||
id="d-ntbar"
|
||
style="width: 0%; background: linear-gradient(90deg, var(--accent2), #ffb020)"
|
||
></div>
|
||
</div>
|
||
<div class="temp-edit" style="margin-top: 10px">
|
||
<input
|
||
type="number"
|
||
class="temp-input"
|
||
id="p-nozzle-inp"
|
||
placeholder="Ziel"
|
||
min="0"
|
||
max="300"
|
||
style="flex: 1"
|
||
/>
|
||
<button class="btn btn-sm btn-accent" onclick="setNozzle()">
|
||
<span class="lbl-set">Set</span>
|
||
</button>
|
||
<button
|
||
class="btn btn-sm"
|
||
style="background: var(--raised); color: var(--txt)"
|
||
onclick="
|
||
document.getElementById('p-nozzle-inp').value = 0;
|
||
setNozzle();
|
||
"
|
||
>
|
||
<span class="lbl-off">Aus</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="temp-block">
|
||
<div class="temp-label" id="d-lbl-bed">Bett</div>
|
||
<div class="temp-row">
|
||
<div class="temp-val" id="d-bt">–</div>
|
||
<div class="temp-unit">°C</div>
|
||
</div>
|
||
<div class="temp-target">→ <span id="d-bt-t">0</span>°C</div>
|
||
<div class="progress-bar" style="margin: 8px 0 0">
|
||
<div
|
||
class="progress-fill"
|
||
id="d-btbar"
|
||
style="width: 0%; background: linear-gradient(90deg, #ff6b35, var(--warn))"
|
||
></div>
|
||
</div>
|
||
<div class="temp-edit" style="margin-top: 10px">
|
||
<input
|
||
type="number"
|
||
class="temp-input"
|
||
id="p-bed-inp"
|
||
placeholder="Ziel"
|
||
min="0"
|
||
max="120"
|
||
style="flex: 1"
|
||
/>
|
||
<button class="btn btn-sm btn-accent" onclick="setBed()">
|
||
<span class="lbl-set">Set</span>
|
||
</button>
|
||
<button
|
||
class="btn btn-sm"
|
||
style="background: var(--raised); color: var(--txt)"
|
||
onclick="
|
||
document.getElementById('p-bed-inp').value = 0;
|
||
setBed();
|
||
"
|
||
>
|
||
<span class="lbl-off">Aus</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div style="margin-top: 14px">
|
||
<div
|
||
style="font-size: 10px; color: var(--txt2); margin-bottom: 4px"
|
||
id="d-chart-label"
|
||
>
|
||
Verlauf (letzte 60 Messungen)
|
||
</div>
|
||
<canvas
|
||
id="d-chart"
|
||
width="800"
|
||
height="120"
|
||
style="width: 100%; height: 120px; background: var(--raised); border-radius: 8px"
|
||
></canvas>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Achsensteuerung -->
|
||
<div class="card">
|
||
<div class="card-title">
|
||
<span>✛</span> <span id="ptitle-motion-xy">XY-Achsen</span>
|
||
</div>
|
||
<div class="joypad">
|
||
<div></div>
|
||
<button class="joy" onclick="move(1, 1, getStep())" title="Y+">▲</button>
|
||
<div></div>
|
||
<button class="joy" onclick="move(0, -1, getStep())" title="X−">◀</button>
|
||
<button class="joy home" onclick="homeAll()" title="Home All">⌂</button>
|
||
<button class="joy" onclick="move(0, 1, getStep())" title="X+">▶</button>
|
||
<div></div>
|
||
<button class="joy" onclick="move(1, -1, getStep())" title="Y−">▼</button>
|
||
<div></div>
|
||
</div>
|
||
<div class="step-btns">
|
||
<button class="step-btn" onclick="setStep(this, 0.1)">0.1</button>
|
||
<button class="step-btn active" onclick="setStep(this, 1)">1</button>
|
||
<button class="step-btn" onclick="setStep(this, 5)">5</button>
|
||
<button class="step-btn" onclick="setStep(this, 10)">10 mm</button>
|
||
</div>
|
||
<div class="home-btns">
|
||
<button
|
||
class="btn btn-sm"
|
||
style="background: var(--raised); color: var(--txt)"
|
||
onclick="homeZ()"
|
||
>
|
||
<span class="lbl-home-z">Home Z</span>
|
||
</button>
|
||
<button
|
||
class="btn btn-sm"
|
||
style="background: var(--raised); color: var(--txt)"
|
||
onclick="homeXY()"
|
||
>
|
||
<span class="lbl-home-xy">Home XY</span>
|
||
</button>
|
||
<button class="btn btn-sm btn-accent" onclick="homeAll()">
|
||
<span class="lbl-home-all">Home All</span>
|
||
</button>
|
||
<button
|
||
class="btn btn-sm"
|
||
style="background: var(--raised); color: var(--txt)"
|
||
onclick="disableMotors()"
|
||
>
|
||
<span class="lbl-disable-motors">Motors Off</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-title"><span>↕</span> <span id="ptitle-motion-z">Z-Achse</span></div>
|
||
<div
|
||
class="joypad"
|
||
style="grid-template-columns: 52px; grid-template-rows: repeat(2, 52px)"
|
||
>
|
||
<button class="joy" onclick="move(2, 1, getStep())" title="Z+">▲</button>
|
||
<button class="joy" onclick="move(2, -1, getStep())" title="Z−">▼</button>
|
||
</div>
|
||
<div style="text-align: center; margin-top: 8px; font-size: 12px; color: var(--txt2)">
|
||
<span class="lbl-step">Schrittweite:</span> <span id="step-display">1</span> mm
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Print Speed -->
|
||
<div class="card">
|
||
<div class="card-title">
|
||
<span>🏎</span> <span id="d-card-speed">Druckgeschwindigkeit</span>
|
||
</div>
|
||
<div style="display: flex; gap: 8px; margin-top: 4px">
|
||
<button class="spd-btn" id="d-spd-1" onclick="setSpeed(1)">
|
||
<span class="spd-icon">🐢</span>
|
||
<span id="d-spd-lbl-1">Leise</span>
|
||
</button>
|
||
<button class="spd-btn spd-active" id="d-spd-2" onclick="setSpeed(2)">
|
||
<span class="spd-icon">⚡</span>
|
||
<span id="d-spd-lbl-2">Normal</span>
|
||
</button>
|
||
<button class="spd-btn" id="d-spd-3" onclick="setSpeed(3)">
|
||
<span class="spd-icon">🚀</span>
|
||
<span id="d-spd-lbl-3">Sport</span>
|
||
</button>
|
||
</div>
|
||
<div class="spd-bar" style="margin-top: 12px">
|
||
<div class="spd-bar-fill" id="d-spd-bar" style="width: 50%"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Lüfter -->
|
||
<div class="card">
|
||
<div class="card-title"><span>🌀</span> <span id="d-card-lightfan">Lüfter</span></div>
|
||
<div class="slider-row">
|
||
<input
|
||
type="range"
|
||
class="slider"
|
||
min="0"
|
||
max="100"
|
||
value="0"
|
||
id="d-fan"
|
||
oninput="document.getElementById('d-fan-val').textContent = this.value"
|
||
onchange="setFan()"
|
||
/>
|
||
<span class="slider-val" id="d-fan-val">0</span>
|
||
</div>
|
||
<div style="margin-top: 12px; display: flex; gap: 8px; flex-wrap: wrap">
|
||
<button
|
||
class="btn btn-sm"
|
||
id="d-fan-off"
|
||
style="background: var(--raised); color: var(--txt)"
|
||
onclick="quickFan(0)"
|
||
>
|
||
Aus
|
||
</button>
|
||
<button
|
||
class="btn btn-sm"
|
||
style="background: var(--raised); color: var(--txt)"
|
||
onclick="quickFan(25)"
|
||
>
|
||
25%
|
||
</button>
|
||
<button
|
||
class="btn btn-sm"
|
||
style="background: var(--raised); color: var(--txt)"
|
||
onclick="quickFan(50)"
|
||
>
|
||
50%
|
||
</button>
|
||
<button
|
||
class="btn btn-sm"
|
||
style="background: var(--raised); color: var(--txt)"
|
||
onclick="quickFan(75)"
|
||
>
|
||
75%
|
||
</button>
|
||
<button class="btn btn-sm btn-accent" onclick="quickFan(100)">100%</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="d-ace-dry-wrap" style="display: none">
|
||
<div id="d-ace-dry-grid" style="display: contents"></div>
|
||
</div>
|
||
|
||
<!-- AMS -->
|
||
<div class="card" style="grid-column: 1/-1" id="d-ams-card">
|
||
<div class="card-title"><span>◫</span> <span id="d-card-ams">Filament</span></div>
|
||
<div class="ams-slots" id="ams-slots">
|
||
<div
|
||
style="grid-column: 1/-1; text-align: center; color: var(--txt2); padding: 20px"
|
||
id="ams-no-data"
|
||
>
|
||
Keine AMS-Daten empfangen
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══ CONSOLE ═══ -->
|
||
<!-- ═══ DRUCKER ═══ -->
|
||
<div class="panel" id="panel-printers">
|
||
<div class="card">
|
||
<div
|
||
class="card-title"
|
||
style="
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 12px;
|
||
"
|
||
>
|
||
<span id="printers-panel-title">🖨 Drucker</span>
|
||
<div style="display: flex; gap: 8px">
|
||
<button
|
||
onclick="openAddPrinterDialog()"
|
||
style="
|
||
font-size: 12px;
|
||
padding: 4px 12px;
|
||
background: var(--accent);
|
||
border: none;
|
||
border-radius: 6px;
|
||
color: #fff;
|
||
cursor: pointer;
|
||
font-weight: 600;
|
||
"
|
||
>
|
||
+ <span id="add-printer-btn-label">Drucker hinzufügen</span>
|
||
</button>
|
||
<button
|
||
onclick="loadPrinterTab()"
|
||
style="
|
||
font-size: 12px;
|
||
padding: 4px 12px;
|
||
background: var(--raised);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
color: var(--txt2);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
↻
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div
|
||
id="printers-grid"
|
||
style="
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||
gap: 14px;
|
||
"
|
||
></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══ GCODE STORE ═══ -->
|
||
<div class="panel" id="panel-store">
|
||
<div class="card">
|
||
<div
|
||
class="card-title"
|
||
style="
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 10px;
|
||
"
|
||
>
|
||
<span id="store-panel-title">🗂 Datei-Browser</span>
|
||
<button
|
||
id="store-refresh-btn"
|
||
onclick="loadStore()"
|
||
style="
|
||
font-size: 12px;
|
||
padding: 4px 12px;
|
||
background: var(--raised);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
color: var(--txt2);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
↻ Aktualisieren
|
||
</button>
|
||
</div>
|
||
<div style="display: flex; gap: 8px; margin-bottom: 12px; flex-wrap: wrap">
|
||
<input
|
||
id="store-search"
|
||
type="text"
|
||
placeholder="🔍 Suche…"
|
||
oninput="renderStore()"
|
||
style="
|
||
flex: 1;
|
||
min-width: 140px;
|
||
padding: 6px 10px;
|
||
background: var(--raised);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
color: var(--txt);
|
||
font-size: 13px;
|
||
"
|
||
/>
|
||
<select
|
||
id="store-filter"
|
||
onchange="renderStore()"
|
||
style="
|
||
padding: 6px 8px;
|
||
background: var(--raised);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
color: var(--txt);
|
||
font-size: 13px;
|
||
"
|
||
>
|
||
<option value="all" id="sf-all">Alle</option>
|
||
<option value="completed" id="sf-ok">✓ Erfolgreich</option>
|
||
<option value="failed" id="sf-err">✗ Fehler</option>
|
||
<option value="never" id="sf-new">Neu</option>
|
||
</select>
|
||
<select
|
||
id="store-sort"
|
||
onchange="renderStore()"
|
||
style="
|
||
padding: 6px 8px;
|
||
background: var(--raised);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
color: var(--txt);
|
||
font-size: 13px;
|
||
"
|
||
>
|
||
<option value="date_desc" id="ss-date">↓ Datum</option>
|
||
<option value="name_asc" id="ss-name">A–Z Name</option>
|
||
<option value="duration_asc" id="ss-dur">⏱ Druckzeit</option>
|
||
</select>
|
||
</div>
|
||
<div
|
||
id="store-upload-zone"
|
||
onclick="document.getElementById('store-upload-input').click()"
|
||
ondragover="
|
||
event.preventDefault();
|
||
this.classList.add('drag-over');
|
||
"
|
||
ondragleave="this.classList.remove('drag-over')"
|
||
ondrop="
|
||
event.preventDefault();
|
||
this.classList.remove('drag-over');
|
||
uploadGcode(event.dataTransfer.files[0]);
|
||
"
|
||
>
|
||
<input
|
||
type="file"
|
||
id="store-upload-input"
|
||
accept=".gcode,.bgcode"
|
||
style="display: none"
|
||
onchange="
|
||
uploadGcode(this.files[0]);
|
||
this.value = '';
|
||
"
|
||
/>
|
||
<span id="store-upload-icon">⬆</span>
|
||
<span id="store-upload-label"
|
||
><span id="store-upload-label-prefix">GCode hierher ziehen oder </span
|
||
><u id="store-upload-label-browse">durchsuchen</u></span
|
||
>
|
||
<span id="store-upload-status" style="display: none"></span>
|
||
</div>
|
||
<div
|
||
id="store-empty"
|
||
style="
|
||
display: none;
|
||
color: var(--txt2);
|
||
text-align: center;
|
||
padding: 40px 0;
|
||
font-size: 14px;
|
||
"
|
||
></div>
|
||
<div
|
||
id="store-grid"
|
||
style="
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||
gap: 14px;
|
||
"
|
||
></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="panel" id="panel-console">
|
||
<div class="card">
|
||
<div
|
||
class="card-title"
|
||
style="display: flex; justify-content: space-between; align-items: center"
|
||
>
|
||
<span><span>≡</span> <span id="ptitle-console">Ereignis-Log</span></span>
|
||
<a
|
||
id="btn-log-dl"
|
||
href="/api/log/download"
|
||
download
|
||
style="
|
||
font-size: 12px;
|
||
padding: 4px 10px;
|
||
background: var(--raised);
|
||
border-radius: 6px;
|
||
color: var(--txt2);
|
||
text-decoration: none;
|
||
"
|
||
>⬇ Download</a
|
||
>
|
||
</div>
|
||
<div
|
||
style="
|
||
display: flex;
|
||
gap: 6px;
|
||
margin-bottom: 6px;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
"
|
||
>
|
||
<input
|
||
id="log-filter"
|
||
type="text"
|
||
placeholder="Filter…"
|
||
oninput="renderLog()"
|
||
style="
|
||
flex: 1;
|
||
min-width: 120px;
|
||
padding: 5px 10px;
|
||
background: var(--raised);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
color: var(--txt);
|
||
font-size: 12px;
|
||
font-family: var(--mono);
|
||
"
|
||
/>
|
||
<button
|
||
id="btn-autoscroll"
|
||
onclick="toggleAutoScroll()"
|
||
style="
|
||
font-size: 12px;
|
||
padding: 5px 10px;
|
||
border-radius: 6px;
|
||
border: 1px solid var(--border);
|
||
background: var(--accent);
|
||
color: #fff;
|
||
cursor: pointer;
|
||
white-space: nowrap;
|
||
"
|
||
>
|
||
⬇ Auto
|
||
</button>
|
||
<button
|
||
onclick="
|
||
consoleLogs = [];
|
||
renderLog();
|
||
"
|
||
style="
|
||
font-size: 12px;
|
||
padding: 5px 10px;
|
||
border-radius: 6px;
|
||
border: 1px solid var(--border);
|
||
background: var(--raised);
|
||
color: var(--txt2);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
✕ Clear
|
||
</button>
|
||
</div>
|
||
<div style="display: flex; gap: 5px; margin-bottom: 8px; flex-wrap: wrap">
|
||
<span
|
||
style="font-size: 11px; color: var(--txt2); align-self: center; margin-right: 2px"
|
||
>Dir:</span
|
||
>
|
||
<button
|
||
class="log-dir-btn active"
|
||
id="logdir-all"
|
||
onclick="setLogDir('all')"
|
||
style="
|
||
font-size: 11px;
|
||
padding: 3px 9px;
|
||
border-radius: 5px;
|
||
border: 1px solid var(--border);
|
||
cursor: pointer;
|
||
"
|
||
></button>
|
||
<button
|
||
class="log-dir-btn"
|
||
id="logdir-rx"
|
||
onclick="setLogDir('rx')"
|
||
style="
|
||
font-size: 11px;
|
||
padding: 3px 9px;
|
||
border-radius: 5px;
|
||
border: 1px solid var(--border);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
RX
|
||
</button>
|
||
<button
|
||
class="log-dir-btn"
|
||
id="logdir-tx"
|
||
onclick="setLogDir('tx')"
|
||
style="
|
||
font-size: 11px;
|
||
padding: 3px 9px;
|
||
border-radius: 5px;
|
||
border: 1px solid var(--border);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
TX
|
||
</button>
|
||
<span
|
||
style="
|
||
font-size: 11px;
|
||
color: var(--txt2);
|
||
align-self: center;
|
||
margin-left: 6px;
|
||
margin-right: 2px;
|
||
"
|
||
id="log-lbl-level"
|
||
>Level:</span
|
||
>
|
||
<button
|
||
class="log-lvl-btn active"
|
||
id="loglvl-all"
|
||
onclick="setLogLevel('all')"
|
||
style="
|
||
font-size: 11px;
|
||
padding: 3px 9px;
|
||
border-radius: 5px;
|
||
border: 1px solid var(--border);
|
||
cursor: pointer;
|
||
"
|
||
></button>
|
||
<button
|
||
class="log-lvl-btn"
|
||
id="loglvl-err"
|
||
onclick="setLogLevel('err')"
|
||
style="
|
||
font-size: 11px;
|
||
padding: 3px 9px;
|
||
border-radius: 5px;
|
||
border: 1px solid var(--border);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
⛔ Errors
|
||
</button>
|
||
<button
|
||
class="log-lvl-btn"
|
||
id="loglvl-warn"
|
||
onclick="setLogLevel('warn')"
|
||
style="
|
||
font-size: 11px;
|
||
padding: 3px 9px;
|
||
border-radius: 5px;
|
||
border: 1px solid var(--border);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
⚠ Warn
|
||
</button>
|
||
<span
|
||
style="
|
||
font-size: 11px;
|
||
color: var(--txt2);
|
||
align-self: center;
|
||
margin-left: 6px;
|
||
margin-right: 2px;
|
||
"
|
||
>Topic:</span
|
||
>
|
||
<button
|
||
class="log-topic-btn"
|
||
data-topic="multiColorBox"
|
||
onclick="setLogTopic('multiColorBox')"
|
||
style="
|
||
font-size: 11px;
|
||
padding: 3px 9px;
|
||
border-radius: 5px;
|
||
border: 1px solid var(--border);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
AMS
|
||
</button>
|
||
<button
|
||
class="log-topic-btn"
|
||
data-topic="print"
|
||
onclick="setLogTopic('print')"
|
||
style="
|
||
font-size: 11px;
|
||
padding: 3px 9px;
|
||
border-radius: 5px;
|
||
border: 1px solid var(--border);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
print
|
||
</button>
|
||
<button
|
||
class="log-topic-btn"
|
||
data-topic="info"
|
||
onclick="setLogTopic('info')"
|
||
style="
|
||
font-size: 11px;
|
||
padding: 3px 9px;
|
||
border-radius: 5px;
|
||
border: 1px solid var(--border);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
info
|
||
</button>
|
||
<button
|
||
class="log-topic-btn"
|
||
data-topic="status"
|
||
onclick="setLogTopic('status')"
|
||
style="
|
||
font-size: 11px;
|
||
padding: 3px 9px;
|
||
border-radius: 5px;
|
||
border: 1px solid var(--border);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
status
|
||
</button>
|
||
</div>
|
||
<div
|
||
class="console"
|
||
id="console-log"
|
||
style="height: calc(100vh - 260px); min-height: 160px"
|
||
onscroll="onLogScroll()"
|
||
></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══ EINSTELLUNGEN ═══ -->
|
||
<div class="panel" id="panel-settings">
|
||
<div class="settings-wrap">
|
||
<div class="settings-cats">
|
||
<button
|
||
class="set-cat active"
|
||
id="setcat-connection"
|
||
onclick="showSettingsCat('connection')"
|
||
>
|
||
<span>🔌</span> <span id="setcat-lbl-connection">Verbindung</span>
|
||
</button>
|
||
<button class="set-cat" id="setcat-printer" onclick="showSettingsCat('printer')">
|
||
<span>🖨</span> <span id="setcat-lbl-printer">Drucker</span>
|
||
</button>
|
||
<button class="set-cat" id="setcat-display" onclick="showSettingsCat('display')">
|
||
<span>🎨</span> <span id="setcat-lbl-display">Darstellung</span>
|
||
</button>
|
||
<button class="set-cat" id="setcat-filament" onclick="showSettingsCat('filament')">
|
||
<span>🧵</span> <span id="setcat-lbl-filament">Filament</span>
|
||
</button>
|
||
<button
|
||
class="set-cat"
|
||
id="setcat-integrations"
|
||
onclick="showSettingsCat('integrations')"
|
||
>
|
||
<span>⚡</span> <span id="setcat-lbl-integrations">Integrationen</span>
|
||
</button>
|
||
<button
|
||
class="set-cat"
|
||
id="setcat-notifications"
|
||
onclick="showSettingsCat('notifications')"
|
||
>
|
||
<span>🔔</span> <span id="setcat-lbl-notifications">Benachrichtigungen</span>
|
||
</button>
|
||
<button class="set-cat" id="setcat-system" onclick="showSettingsCat('system')">
|
||
<span>⚙</span> <span id="setcat-lbl-system">System</span>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="settings-content">
|
||
<!-- Verbindung -->
|
||
<div class="set-group active" id="setgrp-connection">
|
||
<div class="card">
|
||
<div class="card-title">
|
||
<span>🔌</span> <span id="modal-sec-connection">Verbindung</span>
|
||
</div>
|
||
<div class="modal-field" style="margin-bottom: 12px">
|
||
<label id="lbl-printer-name" style="font-weight: 600">Drucker-Name</label>
|
||
<input type="text" id="s-printer-name" placeholder="z.B. Kobra X Links" />
|
||
</div>
|
||
<div class="modal-field">
|
||
<label id="lbl-printer-ip">Drucker-IP</label>
|
||
<input type="text" id="s-printer-ip" placeholder="192.168.x.x" />
|
||
<small id="lbl-ip-hint" style="color: #f80; display: none"></small>
|
||
</div>
|
||
<div class="modal-field">
|
||
<label id="lbl-mqtt-port">MQTT-Port</label>
|
||
<input type="number" id="s-mqtt-port" placeholder="9883" />
|
||
</div>
|
||
<div class="modal-field">
|
||
<label id="lbl-username">MQTT-Benutzername</label>
|
||
<input
|
||
type="text"
|
||
id="s-username"
|
||
placeholder="userXXXXXXXX"
|
||
autocomplete="new-password"
|
||
/>
|
||
</div>
|
||
<div class="modal-field">
|
||
<label id="lbl-password">MQTT-Passwort</label>
|
||
<input type="password" id="s-password" autocomplete="new-password" />
|
||
</div>
|
||
<div class="modal-field">
|
||
<label id="lbl-device-id">Device-ID</label>
|
||
<input type="text" id="s-device-id" placeholder="32 Hex-Zeichen" />
|
||
</div>
|
||
<div class="modal-field">
|
||
<label id="lbl-mode-id">Mode-ID</label>
|
||
<input type="text" id="s-mode-id" placeholder="20030" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Drucker -->
|
||
<div class="set-group" id="setgrp-printer">
|
||
<div class="card">
|
||
<div class="card-title">
|
||
<span>🖨</span> <span id="modal-sec-print">Druckeinstellungen</span>
|
||
</div>
|
||
<div class="modal-field">
|
||
<label id="lbl-default-slot">Standard-Slot (Einfarbdruck)</label>
|
||
<select id="s-default-slot">
|
||
<option value="auto" id="opt-slot-auto">Auto (alle belegten Slots)</option>
|
||
<option value="0" id="opt-slot-0">Slot 1</option>
|
||
<option value="1" id="opt-slot-1">Slot 2</option>
|
||
<option value="2" id="opt-slot-2">Slot 3</option>
|
||
<option value="3" id="opt-slot-3">Slot 4</option>
|
||
</select>
|
||
</div>
|
||
<div
|
||
class="modal-field"
|
||
style="flex-direction: row; align-items: center; gap: 10px"
|
||
>
|
||
<input type="checkbox" id="s-auto-leveling" style="width: auto; margin: 0" />
|
||
<label
|
||
id="lbl-auto-leveling"
|
||
style="margin: 0; cursor: pointer"
|
||
for="s-auto-leveling"
|
||
>Auto-Leveling vor Druck</label
|
||
>
|
||
</div>
|
||
<div class="modal-field">
|
||
<label id="lbl-file-ready-mode">Nach Upload: Druckstart-Verhalten</label>
|
||
<select id="s-file-ready-mode">
|
||
<option value="1" id="opt-file-ready-dialog">Print-Dialog</option>
|
||
<option value="0" id="opt-file-ready-banner">Print-Leiste</option>
|
||
</select>
|
||
</div>
|
||
<div
|
||
class="modal-field"
|
||
style="flex-direction: row; align-items: center; gap: 10px"
|
||
>
|
||
<input type="checkbox" id="s-camera-on-print" style="width: auto; margin: 0" />
|
||
<label
|
||
id="lbl-camera-on-print"
|
||
style="margin: 0; cursor: pointer"
|
||
for="s-camera-on-print"
|
||
>Kamera bei Druckstart einschalten</label
|
||
>
|
||
</div>
|
||
<div
|
||
class="modal-field"
|
||
style="flex-direction: row; align-items: center; gap: 10px"
|
||
>
|
||
<input
|
||
type="checkbox"
|
||
id="s-web-upload-warning"
|
||
style="width: auto; margin: 0"
|
||
/>
|
||
<label
|
||
id="lbl-web-upload-warning"
|
||
style="margin: 0; cursor: pointer"
|
||
for="s-web-upload-warning"
|
||
>Warnung bei Web-Upload-Druck anzeigen</label
|
||
>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Darstellung -->
|
||
<div class="set-group" id="setgrp-display">
|
||
<div class="card">
|
||
<div class="card-title">
|
||
<span>🎨</span> <span id="setcat-lbl-display2">Darstellung</span>
|
||
</div>
|
||
<div class="modal-field">
|
||
<label id="lbl-set-lang">Sprache</label>
|
||
<select id="s-lang-select" onchange="setLanguage(this.value)">
|
||
<option value="de">Deutsch</option>
|
||
<option value="en">English</option>
|
||
<option value="es">Espanol</option>
|
||
<option value="fr">Français</option>
|
||
<option value="it">Italiano</option>
|
||
<option value="zh-cn">中文(简体)</option>
|
||
</select>
|
||
</div>
|
||
<div
|
||
class="modal-field"
|
||
style="flex-direction: row; align-items: center; gap: 10px"
|
||
>
|
||
<button
|
||
class="btn btn-sm"
|
||
style="background: var(--raised); color: var(--txt)"
|
||
onclick="toggleTheme()"
|
||
>
|
||
<span id="lbl-set-theme">Hell / Dunkel umschalten</span>
|
||
</button>
|
||
</div>
|
||
<div class="modal-field">
|
||
<label id="lbl-poll-interval">Poll-Intervall (Sekunden)</label>
|
||
<input
|
||
type="number"
|
||
id="s-poll-interval"
|
||
min="1"
|
||
max="60"
|
||
step="1"
|
||
placeholder="3"
|
||
oninput="onPollIntervalInput()"
|
||
/>
|
||
<small style="color: var(--txt2)" id="lbl-poll-hint"
|
||
>Wie oft die Bridge den Drucker-Status abfragt</small
|
||
>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Filament -->
|
||
<div class="set-group" id="setgrp-filament">
|
||
<div class="card">
|
||
<div class="card-title">
|
||
<span>🧵</span> <span id="modal-sec-orca-profiles">OrcaSlicer-Profile</span>
|
||
</div>
|
||
<div
|
||
style="font-size: 11px; color: var(--txt2); margin-bottom: 8px"
|
||
id="orca-profiles-hint"
|
||
>
|
||
Eigene Profile aus OrcaSlicer importieren (User-Dir öffnen via Help → Show
|
||
Configuration Folder)
|
||
</div>
|
||
<div
|
||
id="orca-profiles-list"
|
||
style="margin-bottom: 8px; font-size: 12px; color: var(--txt2)"
|
||
></div>
|
||
<button
|
||
class="btn btn-sm"
|
||
id="btn-orca-profiles-import"
|
||
onclick="openProfileImport()"
|
||
style="background: var(--raised); color: var(--txt)"
|
||
>
|
||
⬆ <span id="lbl-orca-profiles-import">Profile importieren</span>
|
||
</button>
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-title">
|
||
<span>🎯</span>
|
||
<span id="lbl-filament-mapping">Filament-Profil-Mapping (pro Slot)</span>
|
||
</div>
|
||
<div
|
||
style="font-size: 11px; color: var(--txt2); margin-bottom: 8px"
|
||
id="filament-mapping-hint"
|
||
>
|
||
Festes Orca-Profil pro AMS-Slot. Beim Slicer-Sync sendet die Bridge dieses
|
||
Profil statt „Generic".
|
||
</div>
|
||
<div id="filament-mapping-list"></div>
|
||
<button
|
||
class="btn btn-sm"
|
||
style="background: var(--accent); color: #fff; margin-top: 8px"
|
||
onclick="saveFilamentMapping()"
|
||
>
|
||
<span id="lbl-filament-mapping-save">Mapping speichern</span>
|
||
</button>
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-title">
|
||
<span>👁</span>
|
||
<span id="lbl-visible-vendors">Sichtbare Hersteller (Profil-Dropdown)</span>
|
||
</div>
|
||
<div
|
||
style="font-size: 11px; color: var(--txt2); margin-bottom: 8px"
|
||
id="visible-vendors-hint"
|
||
>
|
||
Nur diese Hersteller erscheinen im Slot-Profil-Dropdown. Nichts ausgewählt =
|
||
alle anzeigen. „Generic" und eigene Profile sind immer sichtbar.
|
||
</div>
|
||
<input
|
||
type="text"
|
||
id="vendor-filter-search"
|
||
placeholder="Hersteller suchen…"
|
||
oninput="renderVendorChecklist()"
|
||
style="
|
||
width: 100%;
|
||
padding: 6px 10px;
|
||
margin-bottom: 8px;
|
||
background: var(--raised);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
color: var(--txt);
|
||
font-size: 12px;
|
||
"
|
||
/>
|
||
<div
|
||
id="visible-vendors-list"
|
||
style="
|
||
max-height: 260px;
|
||
overflow-y: auto;
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
padding: 8px;
|
||
"
|
||
></div>
|
||
<button
|
||
class="btn btn-sm"
|
||
style="background: var(--accent); color: #fff; margin-top: 8px"
|
||
onclick="saveVisibleVendors()"
|
||
>
|
||
<span id="lbl-visible-vendors-save">Auswahl speichern</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Integrationen -->
|
||
<div class="set-group" id="setgrp-integrations">
|
||
<!-- Spoolman -->
|
||
<div class="card">
|
||
<div class="card-title">
|
||
<span>🧵</span> <span id="modal-sec-spoolman">Spoolman</span>
|
||
</div>
|
||
<div class="set-row">
|
||
<label id="lbl-spoolman-url">Server-URL</label>
|
||
<input
|
||
type="text"
|
||
id="s-spoolman-url"
|
||
placeholder="http://spoolman:7912"
|
||
style="width: 200px"
|
||
/>
|
||
</div>
|
||
<div class="set-row">
|
||
<label id="lbl-spoolman-sync-rate">Sync-Rate (s, 0=aus)</label>
|
||
<input
|
||
type="number"
|
||
id="s-spoolman-sync-rate"
|
||
min="0"
|
||
max="3600"
|
||
value="30"
|
||
style="width: 80px"
|
||
/>
|
||
</div>
|
||
<div
|
||
id="spoolman-status-row"
|
||
style="margin-top: 6px; font-size: 12px; color: var(--txt2)"
|
||
>
|
||
<span id="spoolman-status-dot">●</span> <span id="spoolman-status-lbl"></span>
|
||
</div>
|
||
</div>
|
||
<!-- Obico -->
|
||
<div class="card" style="margin-top: 10px">
|
||
<div class="card-title">
|
||
<span>🕵</span> <span id="modal-sec-obico">Obico</span>
|
||
</div>
|
||
<div
|
||
style="font-size: 12px; color: var(--txt2); line-height: 1.6"
|
||
id="obico-info-box"
|
||
>
|
||
Obico wird über den <code>moonraker-obico</code>-Container konfiguriert.<br />
|
||
Config-Datei:
|
||
<code id="obico-cfg-path"
|
||
>/mnt/dockerdata/KobraXStack/moonraker-obico/moonraker-obico.cfg</code
|
||
>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Benachrichtigungen -->
|
||
<div class="set-group" id="setgrp-notifications">
|
||
<div class="card">
|
||
<div class="card-title">
|
||
<span>🔔</span> <span id="modal-sec-notifications">Benachrichtigungen</span>
|
||
</div>
|
||
<div
|
||
style="font-size: 11px; color: var(--txt2); margin-bottom: 8px"
|
||
id="notif-hint"
|
||
></div>
|
||
<div id="notif-list" style="margin-bottom: 8px"></div>
|
||
<button
|
||
class="btn btn-sm"
|
||
id="btn-notif-add"
|
||
onclick="notifAddRow()"
|
||
style="background: var(--raised); color: var(--txt)"
|
||
>
|
||
+ <span id="lbl-notif-add">Benachrichtigung hinzufügen</span>
|
||
</button>
|
||
<div
|
||
style="
|
||
display: flex;
|
||
gap: 16px;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
margin-top: 10px;
|
||
font-size: 12px;
|
||
"
|
||
>
|
||
<span
|
||
id="lbl-notif-interval"
|
||
style="font-size: 11px; font-weight: 600; white-space: nowrap"
|
||
>Wiederholungsintervall</span
|
||
>
|
||
<span style="white-space: nowrap">
|
||
<input
|
||
type="number"
|
||
id="s-notif-every-min"
|
||
min="0"
|
||
max="999"
|
||
style="width: 52px; display: inline-block"
|
||
/>
|
||
<span id="lbl-notif-min-unit"> min</span>
|
||
</span>
|
||
<span style="white-space: nowrap">
|
||
<input
|
||
type="number"
|
||
id="s-notif-every-layers"
|
||
min="0"
|
||
max="9999"
|
||
style="width: 60px; display: inline-block"
|
||
/>
|
||
<span id="lbl-notif-layers-unit"> Schichten</span>
|
||
</span>
|
||
<span style="font-size: 11px; color: var(--txt2)" id="lbl-notif-zero-off"
|
||
>(0 = aus)</span
|
||
>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- System -->
|
||
<div class="set-group" id="setgrp-system">
|
||
<div class="card">
|
||
<div class="card-title">
|
||
<span>⚙</span> <span id="modal-sec-version">Version</span>
|
||
</div>
|
||
<div class="update-row">
|
||
<span id="s-version-label" style="font-size: 13px; color: var(--txt)">–</span>
|
||
<button
|
||
class="btn btn-sm"
|
||
style="background: var(--raised); color: var(--txt)"
|
||
onclick="checkUpdate()"
|
||
id="btn-update-check"
|
||
>
|
||
🔄 <span id="lbl-update-check">Auf Updates prüfen</span>
|
||
</button>
|
||
</div>
|
||
<div class="update-status" id="update-status" style="margin-top: 6px"></div>
|
||
<button
|
||
class="btn btn-sm btn-accent"
|
||
id="btn-update-apply"
|
||
style="display: none; margin-top: 8px"
|
||
onclick="applyUpdate()"
|
||
>
|
||
<span id="lbl-update-apply">Jetzt installieren</span>
|
||
</button>
|
||
<div
|
||
id="update-changelog"
|
||
style="
|
||
display: none;
|
||
margin-top: 10px;
|
||
background: var(--raised);
|
||
border-radius: 6px;
|
||
padding: 10px;
|
||
font-size: 11px;
|
||
font-family: var(--mono);
|
||
color: var(--txt2);
|
||
white-space: pre-wrap;
|
||
max-height: 180px;
|
||
overflow-y: auto;
|
||
line-height: 1.6;
|
||
"
|
||
></div>
|
||
</div>
|
||
</div>
|
||
|
||
<button
|
||
class="modal-save"
|
||
onclick="saveSettings()"
|
||
id="btn-save-settings"
|
||
style="margin-top: 14px"
|
||
>
|
||
Speichern & Neustart
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
|
||
<nav class="bottom-nav">
|
||
<button class="bnav-btn active" onclick="showPanel('dashboard')" id="bnb-dashboard">
|
||
<span class="bnav-icon">⊞</span>Dashboard
|
||
</button>
|
||
<button
|
||
class="bnav-btn"
|
||
onclick="
|
||
showPanel('printers');
|
||
loadPrinterTab();
|
||
"
|
||
id="bnb-printers"
|
||
>
|
||
<span class="bnav-icon">🖨</span>Drucker
|
||
</button>
|
||
<button
|
||
class="bnav-btn"
|
||
onclick="
|
||
showPanel('store');
|
||
loadStore();
|
||
"
|
||
id="bnb-store"
|
||
>
|
||
<span class="bnav-icon">🗂</span>Browser
|
||
</button>
|
||
<button
|
||
class="bnav-btn"
|
||
onclick="
|
||
showPanel('console');
|
||
clearLogBadge();
|
||
"
|
||
id="bnb-console"
|
||
>
|
||
<span class="bnav-icon">≡</span>Log<span
|
||
id="log-badge-bot"
|
||
style="
|
||
display: none;
|
||
margin-left: 3px;
|
||
background: var(--err);
|
||
color: #fff;
|
||
border-radius: 10px;
|
||
font-size: 10px;
|
||
padding: 1px 4px;
|
||
font-weight: 700;
|
||
"
|
||
></span>
|
||
</button>
|
||
<button class="bnav-btn" onclick="showPanel('settings')" id="bnb-settings">
|
||
<span class="bnav-icon">⚙</span>Setup
|
||
</button>
|
||
</nav>
|
||
|
||
<!-- Web-Upload-Verify-Dialog -->
|
||
<div
|
||
class="modal-overlay"
|
||
id="store-web-verify-dialog"
|
||
onclick="if (event.target === this) closeStoreWebVerifyDialog();"
|
||
>
|
||
<div class="modal-box" style="max-width: 420px; width: 100%">
|
||
<div class="modal-header" style="margin-bottom: 14px">
|
||
<span class="modal-title" id="store-web-verify-title">Datei verifizieren</span>
|
||
<button
|
||
onclick="closeStoreWebVerifyDialog()"
|
||
style="
|
||
background: none;
|
||
border: none;
|
||
font-size: 18px;
|
||
cursor: pointer;
|
||
color: var(--txt2);
|
||
"
|
||
>
|
||
✕
|
||
</button>
|
||
</div>
|
||
<p
|
||
id="store-web-verify-msg"
|
||
style="font-size: 13px; color: var(--txt); margin-bottom: 12px"
|
||
>
|
||
Bitte bestätige, dass diese Datei für den Anycubic Kobra X erstellt wurde.
|
||
</p>
|
||
<div
|
||
id="store-web-verify-status"
|
||
style="font-size: 12px; color: var(--txt2); min-height: 16px; margin-bottom: 8px"
|
||
></div>
|
||
<div style="display: flex; gap: 8px; justify-content: flex-end">
|
||
<button
|
||
id="store-web-verify-abort"
|
||
onclick="closeStoreWebVerifyDialog()"
|
||
style="
|
||
padding: 8px 16px;
|
||
background: var(--raised);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
color: var(--txt);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
Abbrechen
|
||
</button>
|
||
<button
|
||
id="store-web-verify-confirm"
|
||
onclick="confirmStoreWebVerify()"
|
||
style="
|
||
padding: 8px 18px;
|
||
background: var(--accent);
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
font-weight: 600;
|
||
"
|
||
>
|
||
Bestätigen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Filament-Slot-Dialog -->
|
||
<div
|
||
class="modal-overlay"
|
||
id="filament-dialog"
|
||
onclick="if (event.target === this) closeFilamentDialog();"
|
||
>
|
||
<div class="modal-box" style="max-width: 380px; width: 100%">
|
||
<div class="modal-header" style="margin-bottom: 14px">
|
||
<span
|
||
class="modal-title"
|
||
id="fd-title"
|
||
style="font-size: 14px; word-break: break-all"
|
||
></span>
|
||
<button
|
||
onclick="closeFilamentDialog()"
|
||
style="
|
||
background: none;
|
||
border: none;
|
||
font-size: 18px;
|
||
cursor: pointer;
|
||
color: var(--txt2);
|
||
"
|
||
>
|
||
✕
|
||
</button>
|
||
</div>
|
||
<p id="fd-slots-hint" style="font-size: 12px; color: var(--txt2); margin-bottom: 10px">
|
||
GCode-Kanal → AMS-Slot zuweisen:
|
||
</p>
|
||
<div
|
||
id="fd-slots"
|
||
style="display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px"
|
||
></div>
|
||
<div id="fd-objects-section" style="display: none; margin-bottom: 16px">
|
||
<button
|
||
type="button"
|
||
id="fd-objects-toggle"
|
||
onclick="toggleFdObjects()"
|
||
style="
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
width: 100%;
|
||
padding: 8px 10px;
|
||
background: var(--raised);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
color: var(--txt);
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
text-align: left;
|
||
"
|
||
>
|
||
<span id="fd-objects-arrow" style="font-size: 10px; transition: transform 0.15s"
|
||
>▶</span
|
||
>
|
||
<span>✂ <span id="fd-objects-toggle-lbl">Objekte überspringen</span></span>
|
||
<span
|
||
id="fd-objects-count"
|
||
style="margin-left: auto; color: var(--txt2); font-weight: 600"
|
||
></span>
|
||
</button>
|
||
<div id="fd-objects-body" style="display: none; margin-top: 8px">
|
||
<div
|
||
id="fd-objects-svg"
|
||
style="
|
||
display: none;
|
||
background: var(--raised);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
padding: 6px;
|
||
margin-bottom: 8px;
|
||
text-align: center;
|
||
"
|
||
></div>
|
||
<div
|
||
id="fd-objects"
|
||
style="
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
max-height: 140px;
|
||
overflow-y: auto;
|
||
"
|
||
></div>
|
||
</div>
|
||
</div>
|
||
<div
|
||
style="
|
||
margin-bottom: 14px;
|
||
padding: 10px 12px;
|
||
background: var(--raised);
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border);
|
||
"
|
||
>
|
||
<div
|
||
style="
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
color: var(--txt2);
|
||
margin-bottom: 8px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.05em;
|
||
"
|
||
id="fd-options-title"
|
||
>
|
||
Druckoptionen
|
||
</div>
|
||
<div style="display: flex; align-items: center; gap: 8px">
|
||
<input type="checkbox" id="fd-auto-leveling" style="width: auto; margin: 0" />
|
||
<label
|
||
for="fd-auto-leveling"
|
||
style="margin: 0; cursor: pointer; font-size: 13px"
|
||
id="fd-lbl-auto-leveling"
|
||
>Auto-Leveling</label
|
||
>
|
||
</div>
|
||
</div>
|
||
<div
|
||
id="fd-spoolman-section"
|
||
style="
|
||
display: none;
|
||
margin-bottom: 16px;
|
||
border-top: 1px solid var(--border);
|
||
padding-top: 12px;
|
||
"
|
||
>
|
||
<p
|
||
style="
|
||
font-size: 12px;
|
||
color: var(--txt2);
|
||
margin-bottom: 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
"
|
||
>
|
||
<span id="fd-spoolman-lbl">🧵 Spoolman</span>
|
||
<span id="fd-spoolman-loading" style="display: none; font-size: 10px">…</span>
|
||
</p>
|
||
<div id="fd-spoolman-rows" style="display: flex; flex-direction: column; gap: 6px"></div>
|
||
</div>
|
||
<div style="display: flex; gap: 8px; justify-content: flex-end">
|
||
<button
|
||
id="fd-cancel"
|
||
onclick="closeFilamentDialog()"
|
||
style="
|
||
padding: 8px 16px;
|
||
background: var(--raised);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
color: var(--txt);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
Abbrechen
|
||
</button>
|
||
<button
|
||
id="fd-print"
|
||
onclick="confirmFilamentPrint()"
|
||
style="
|
||
padding: 8px 18px;
|
||
background: var(--accent);
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
font-weight: 600;
|
||
"
|
||
>
|
||
▶ Drucken
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Drucker-hinzufügen-Dialog -->
|
||
<div
|
||
class="modal-overlay"
|
||
id="add-printer-dialog"
|
||
onclick="if (event.target === this) closeAddPrinterDialog();"
|
||
>
|
||
<div class="modal-box" style="max-width: 380px; width: 100%">
|
||
<div class="modal-header" style="margin-bottom: 14px">
|
||
<span class="modal-title" id="apd-title">Drucker hinzufügen</span>
|
||
<button
|
||
onclick="closeAddPrinterDialog()"
|
||
style="
|
||
background: none;
|
||
border: none;
|
||
font-size: 18px;
|
||
cursor: pointer;
|
||
color: var(--txt2);
|
||
"
|
||
>
|
||
✕
|
||
</button>
|
||
</div>
|
||
<label
|
||
id="apd-lbl-ip"
|
||
style="display: block; font-size: 12px; color: var(--txt2); margin-bottom: 4px"
|
||
>Drucker-IP</label
|
||
>
|
||
<input
|
||
type="text"
|
||
id="apd-ip"
|
||
placeholder="192.168.1.100"
|
||
style="
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
padding: 8px;
|
||
background: var(--raised);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
color: var(--txt);
|
||
margin-bottom: 10px;
|
||
"
|
||
/>
|
||
<label
|
||
id="apd-lbl-name"
|
||
style="display: block; font-size: 12px; color: var(--txt2); margin-bottom: 4px"
|
||
>Name (optional)</label
|
||
>
|
||
<input
|
||
type="text"
|
||
id="apd-name"
|
||
placeholder="z.B. Kobra X Wohnzimmer"
|
||
style="
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
padding: 8px;
|
||
background: var(--raised);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
color: var(--txt);
|
||
margin-bottom: 6px;
|
||
"
|
||
/>
|
||
<div
|
||
id="apd-status"
|
||
style="font-size: 12px; margin: 8px 0; min-height: 16px; color: var(--txt2)"
|
||
></div>
|
||
<div style="display: flex; gap: 8px; justify-content: flex-end">
|
||
<button
|
||
id="apd-cancel"
|
||
onclick="closeAddPrinterDialog()"
|
||
style="
|
||
padding: 8px 16px;
|
||
background: var(--raised);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
color: var(--txt);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
Abbrechen
|
||
</button>
|
||
<button
|
||
id="apd-confirm"
|
||
onclick="confirmAddPrinter()"
|
||
style="
|
||
padding: 8px 18px;
|
||
background: var(--accent);
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
font-weight: 600;
|
||
"
|
||
>
|
||
Hinzufügen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Mid-Print Skip-Dialog -->
|
||
<div
|
||
class="modal-overlay"
|
||
id="skip-dialog"
|
||
onclick="if (event.target === this) closeSkipDialog();"
|
||
>
|
||
<div class="modal-box" style="max-width: 420px; width: 100%">
|
||
<div class="modal-header" style="margin-bottom: 14px">
|
||
<span class="modal-title" id="skip-title">✂ Objekte überspringen</span>
|
||
<button
|
||
onclick="closeSkipDialog()"
|
||
style="
|
||
background: none;
|
||
border: none;
|
||
font-size: 18px;
|
||
cursor: pointer;
|
||
color: var(--txt2);
|
||
"
|
||
>
|
||
✕
|
||
</button>
|
||
</div>
|
||
<p id="skip-hint" style="font-size: 12px; color: var(--txt2); margin-bottom: 10px">
|
||
Objekte abwählen, die nicht weiter gedruckt werden sollen:
|
||
</p>
|
||
<div
|
||
id="skip-svg"
|
||
style="
|
||
display: none;
|
||
background: var(--raised);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
padding: 6px;
|
||
margin-bottom: 10px;
|
||
text-align: center;
|
||
"
|
||
></div>
|
||
<div
|
||
id="skip-list"
|
||
style="
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
max-height: 200px;
|
||
overflow-y: auto;
|
||
margin-bottom: 12px;
|
||
"
|
||
></div>
|
||
<div
|
||
id="skip-status"
|
||
style="font-size: 12px; color: var(--txt2); min-height: 16px; margin-bottom: 8px"
|
||
></div>
|
||
<div style="display: flex; gap: 8px; justify-content: flex-end">
|
||
<button
|
||
onclick="closeSkipDialog()"
|
||
style="
|
||
padding: 8px 16px;
|
||
background: var(--raised);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
color: var(--txt);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
Abbrechen
|
||
</button>
|
||
<button
|
||
id="skip-confirm"
|
||
onclick="confirmSkip()"
|
||
style="
|
||
padding: 8px 18px;
|
||
background: var(--accent);
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
font-weight: 600;
|
||
"
|
||
>
|
||
Überspringen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ACE Dryer Temp/Time Settings Dialog -->
|
||
<div
|
||
class="modal-overlay"
|
||
id="ace-dry-dialog"
|
||
onclick="if (event.target === this) closeAceDryDialog();"
|
||
>
|
||
<div class="modal-box" style="max-width: 560px; width: 100%">
|
||
<div class="modal-header" style="margin-bottom: 10px">
|
||
<span class="modal-title" id="ace-dry-dialog-title">Dryer Temp/Time Settings</span>
|
||
<button
|
||
onclick="closeAceDryDialog()"
|
||
style="
|
||
background: none;
|
||
border: none;
|
||
font-size: 18px;
|
||
cursor: pointer;
|
||
color: var(--txt2);
|
||
"
|
||
>
|
||
✕
|
||
</button>
|
||
</div>
|
||
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 8px">
|
||
<label
|
||
id="ace-dry-dialog-temp-label"
|
||
style="min-width: 190px; font-size: 12px; color: var(--txt)"
|
||
>Temperature (30-80°C)</label
|
||
>
|
||
<input
|
||
id="ace-dry-dialog-temp"
|
||
type="number"
|
||
min="30"
|
||
max="80"
|
||
step="1"
|
||
oninput="aceDryDialogInputsChanged()"
|
||
style="
|
||
width: 130px;
|
||
padding: 8px;
|
||
background: var(--raised);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
color: var(--txt);
|
||
text-align: center;
|
||
"
|
||
value="45"
|
||
/>
|
||
</div>
|
||
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 16px">
|
||
<label
|
||
id="ace-dry-dialog-time-label"
|
||
style="min-width: 190px; font-size: 12px; color: var(--txt)"
|
||
>Rem. Time (h:m:s)</label
|
||
>
|
||
<div style="display: flex; align-items: center; gap: 8px">
|
||
<input
|
||
id="ace-dry-dialog-h"
|
||
type="number"
|
||
min="0"
|
||
max="24"
|
||
step="1"
|
||
value="4"
|
||
oninput="aceDryDialogInputsChanged()"
|
||
style="
|
||
width: 70px;
|
||
padding: 8px;
|
||
background: var(--raised);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
color: var(--txt);
|
||
text-align: center;
|
||
"
|
||
/>
|
||
<span style="color: var(--txt2)">:</span>
|
||
<input
|
||
id="ace-dry-dialog-m"
|
||
type="number"
|
||
min="0"
|
||
max="59"
|
||
step="1"
|
||
value="0"
|
||
oninput="aceDryDialogInputsChanged()"
|
||
style="
|
||
width: 70px;
|
||
padding: 8px;
|
||
background: var(--raised);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
color: var(--txt);
|
||
text-align: center;
|
||
"
|
||
/>
|
||
<span style="color: var(--txt2)">:</span>
|
||
<input
|
||
id="ace-dry-dialog-s"
|
||
type="number"
|
||
min="0"
|
||
max="59"
|
||
step="1"
|
||
value="0"
|
||
oninput="aceDryDialogInputsChanged()"
|
||
style="
|
||
width: 70px;
|
||
padding: 8px;
|
||
background: var(--raised);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
color: var(--txt);
|
||
text-align: center;
|
||
"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div
|
||
style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 8px"
|
||
>
|
||
<button
|
||
class="ace-dry-preset-btn"
|
||
data-preset="pla"
|
||
onclick="aceDryDialogPreset('pla')"
|
||
style="
|
||
padding: 8px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border);
|
||
background: var(--raised);
|
||
color: var(--txt2);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
PLA
|
||
</button>
|
||
<button
|
||
class="ace-dry-preset-btn"
|
||
data-preset="pla_plus"
|
||
onclick="aceDryDialogPreset('pla_plus')"
|
||
style="
|
||
padding: 8px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border);
|
||
background: var(--raised);
|
||
color: var(--txt2);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
PLA+
|
||
</button>
|
||
<button
|
||
class="ace-dry-preset-btn"
|
||
data-preset="petg"
|
||
onclick="aceDryDialogPreset('petg')"
|
||
style="
|
||
padding: 8px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border);
|
||
background: var(--raised);
|
||
color: var(--txt2);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
PETG
|
||
</button>
|
||
<button
|
||
class="ace-dry-preset-btn"
|
||
data-preset="tpu"
|
||
onclick="aceDryDialogPreset('tpu')"
|
||
style="
|
||
padding: 8px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border);
|
||
background: var(--raised);
|
||
color: var(--txt2);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
TPU
|
||
</button>
|
||
<button
|
||
class="ace-dry-preset-btn"
|
||
data-preset="abs_asa"
|
||
onclick="aceDryDialogPreset('abs_asa')"
|
||
style="
|
||
padding: 8px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border);
|
||
background: var(--raised);
|
||
color: var(--txt2);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
ABS / ASA
|
||
</button>
|
||
<button
|
||
class="ace-dry-preset-btn"
|
||
data-preset="pa_pc"
|
||
onclick="aceDryDialogPreset('pa_pc')"
|
||
style="
|
||
padding: 8px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border);
|
||
background: var(--raised);
|
||
color: var(--txt2);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
PA / PC
|
||
</button>
|
||
<button
|
||
class="ace-dry-preset-btn"
|
||
data-preset="custom_1"
|
||
onclick="aceDryDialogPreset('custom_1')"
|
||
style="
|
||
padding: 8px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border);
|
||
background: var(--raised);
|
||
color: var(--txt2);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
Custom 1
|
||
</button>
|
||
<button
|
||
class="ace-dry-preset-btn"
|
||
data-preset="custom_2"
|
||
onclick="aceDryDialogPreset('custom_2')"
|
||
style="
|
||
padding: 8px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border);
|
||
background: var(--raised);
|
||
color: var(--txt2);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
Custom 2
|
||
</button>
|
||
<button
|
||
class="ace-dry-preset-btn"
|
||
data-preset="custom_3"
|
||
onclick="aceDryDialogPreset('custom_3')"
|
||
style="
|
||
padding: 8px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border);
|
||
background: var(--raised);
|
||
color: var(--txt2);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
Custom 3
|
||
</button>
|
||
</div>
|
||
<div
|
||
id="ace-dry-dialog-custom-name-row"
|
||
style="display: none; align-items: center; gap: 12px; margin-bottom: 14px"
|
||
>
|
||
<label
|
||
id="ace-dry-dialog-custom-name-label"
|
||
style="min-width: 190px; font-size: 12px; color: var(--txt)"
|
||
>Custom Name</label
|
||
>
|
||
<input
|
||
id="ace-dry-dialog-custom-name"
|
||
type="text"
|
||
maxlength="32"
|
||
oninput="aceDryDialogInputsChanged()"
|
||
style="
|
||
width: 220px;
|
||
padding: 8px;
|
||
background: var(--raised);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
color: var(--txt);
|
||
"
|
||
/>
|
||
</div>
|
||
<div style="display: flex; justify-content: flex-end; gap: 8px">
|
||
<button
|
||
id="ace-dry-dialog-reset-default"
|
||
onclick="resetAceDryPresetToDefault()"
|
||
style="
|
||
display: none;
|
||
padding: 8px 14px;
|
||
background: var(--raised);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
color: var(--txt);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
Reset to Default
|
||
</button>
|
||
<button
|
||
id="ace-dry-dialog-save-preset"
|
||
onclick="saveAceDryPresetAndRestart()"
|
||
style="
|
||
display: none;
|
||
padding: 8px 14px;
|
||
background: var(--warn);
|
||
border: 1px solid transparent;
|
||
border-radius: 8px;
|
||
color: #fff;
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
Save & Restart
|
||
</button>
|
||
<button
|
||
id="ace-dry-dialog-cancel"
|
||
onclick="closeAceDryDialog()"
|
||
style="
|
||
padding: 8px 14px;
|
||
background: var(--raised);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
color: var(--txt);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
Cancel
|
||
</button>
|
||
<button
|
||
id="ace-dry-dialog-confirm"
|
||
onclick="confirmAceDryDialog()"
|
||
style="
|
||
padding: 8px 16px;
|
||
background: var(--accent);
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
font-weight: 600;
|
||
"
|
||
>
|
||
Confirm
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<script src="/kx/ui/app.js"></script>
|
||
<footer
|
||
style="
|
||
text-align: center;
|
||
padding: 12px;
|
||
font-size: 11px;
|
||
color: var(--txt2);
|
||
border-top: 1px solid var(--border);
|
||
margin-top: auto;
|
||
"
|
||
>
|
||
© ViewIT 2026
|
||
</footer>
|
||
</body>
|
||
</html>
|