diff --git a/public/index.css b/public/index.css
index d9bbe48..82358c7 100644
--- a/public/index.css
+++ b/public/index.css
@@ -246,6 +246,67 @@ table td.ts { color: var(--muted); font-size: 11px; }
}
.status-line.err { color: #fca5a5; }
+/* ===== ZEILEN-LÖSCHEN ===== */
+table td.del-col,
+table th.del-col {
+ width: 32px;
+ text-align: center;
+ padding: 2px 4px;
+}
+.btn-row-del {
+ background: none;
+ border: 1px solid transparent;
+ color: var(--muted);
+ border-radius: 4px;
+ padding: 1px 5px;
+ cursor: pointer;
+ font-size: 12px;
+ opacity: 0;
+ transition: opacity 0.1s;
+}
+tr:hover .btn-row-del,
+tr.pending-delete .btn-row-del { opacity: 1; }
+.btn-row-del:hover {
+ color: #fca5a5;
+ border-color: var(--danger-border);
+ background: var(--danger);
+}
+tr.pending-delete td {
+ text-decoration: line-through;
+ color: #fca5a5;
+ opacity: 0.6;
+}
+tr.pending-delete .btn-row-del {
+ color: #fca5a5;
+ border-color: var(--danger-border);
+ background: var(--danger);
+}
+
+/* ===== EDIT-BAR (Abbrechen / Speichern) ===== */
+.edit-bar {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-top: 10px;
+ padding: 8px 12px;
+ background: #1a0a0a;
+ border: 1px solid var(--danger-border);
+ border-radius: 6px;
+}
+.edit-bar-info {
+ font-size: 12px;
+ color: #fca5a5;
+}
+button.danger-confirm {
+ background: var(--danger);
+ border-color: var(--danger-border);
+ color: #fca5a5;
+}
+button.danger-confirm:hover:not(:disabled) {
+ border-color: #fca5a5;
+ color: #fff;
+}
+
/* ===== DOWNLOAD LINK ===== */
.dl-link {
display: inline-block;
diff --git a/public/index.html b/public/index.html
index 25b30cd..3e03f50 100644
--- a/public/index.html
+++ b/public/index.html
@@ -68,13 +68,20 @@
# |
G-Code (gespeichert in Grad) |
Zeitstempel |
+ |
- | Kein aktives Programm |
+ | Kein aktives Programm |
+
+
+
+
+
+
@@ -85,9 +92,11 @@
const REFRESH_MS = 5000; // Auto-Refresh-Intervall
// ─── Zustand ─────────────────────────────────────────────────────────────────
- let currentActiveId = null;
- let selectedId = null; // in der Liste ausgewähltes Item (unabhängig vom aktiven Programm)
- let selectedType = null; // 'file' | 'folder'
+ let currentActiveId = null;
+ let selectedId = null; // in der Liste ausgewähltes Item (unabhängig vom aktiven Programm)
+ let selectedType = null; // 'file' | 'folder'
+ let pendingDeletes = new Set(); // Indices markierter Zeilen (noch nicht gespeichert)
+ let cachedState = null; // letzter bekannter Serverzustand (für Abbrechen)
// ─── DOM-Referenzen ───────────────────────────────────────────────────────────
const elProgCount = document.getElementById('prog-count');
@@ -190,8 +199,35 @@
});
}
+ // ─── Edit-Bar (Abbrechen / Speichern) ───────────────────────────────────────
+ const elEditBar = document.getElementById('edit-bar');
+ const elEditBarInfo = document.getElementById('edit-bar-info');
+
+ function updateEditBar() {
+ const n = pendingDeletes.size;
+ if (n === 0) {
+ elEditBar.style.display = 'none';
+ } else {
+ elEditBar.style.display = 'flex';
+ elEditBarInfo.textContent = `${n} Zeile${n === 1 ? '' : 'n'} zum Löschen markiert`;
+ }
+ }
+
+ function markDelete(index) {
+ if (pendingDeletes.has(index)) {
+ pendingDeletes.delete(index);
+ } else {
+ pendingDeletes.add(index);
+ }
+ // Nur die betroffene Zeile neu stylen statt ganzer Tabelle
+ const row = elLinesBody.querySelector(`tr[data-index="${index}"]`);
+ if (row) row.classList.toggle('pending-delete', pendingDeletes.has(index));
+ updateEditBar();
+ }
+
// ─── Zeilen-Tabelle rendern ──────────────────────────────────────────────────
function renderLines(state) {
+ cachedState = state;
const { programId, cursor, lineCount, lines = [], version } = state;
currentActiveId = programId;
@@ -228,29 +264,45 @@
// Tabelle
if (!lines.length) {
- elLinesBody.innerHTML = `| ${
+ elLinesBody.innerHTML = ` |
| ${
hasProgram ? 'Programm ist leer' : 'Kein aktives Programm — links ein Programm anklicken'
} |
`;
+ updateEditBar();
return;
}
elLinesBody.innerHTML = lines.map((line, i) => {
const { code, ts } = parseLine(line);
- const isCursor = i === cursor;
- return `
+ const isCursor = i === cursor;
+ const isPending = pendingDeletes.has(i);
+ return `
| ${i} |
${isCursor ? '▶ ' : ''}${esc(code)} |
${esc(ts)} |
+ |
`;
}).join('');
- // Zur Cursor-Zeile scrollen
- const cursorRow = elLinesBody.querySelector('.cursor-row');
- if (cursorRow) cursorRow.scrollIntoView({ block: 'nearest' });
+ // Delete-Buttons verdrahten
+ elLinesBody.querySelectorAll('.btn-row-del').forEach(btn => {
+ btn.addEventListener('click', (e) => {
+ e.stopPropagation();
+ markDelete(Number(btn.dataset.index));
+ });
+ });
+
+ // Zur Cursor-Zeile scrollen (nur wenn kein edit-Modus aktiv)
+ if (pendingDeletes.size === 0) {
+ const cursorRow = elLinesBody.querySelector('.cursor-row');
+ if (cursorRow) cursorRow.scrollIntoView({ block: 'nearest' });
+ }
+
+ updateEditBar();
}
// ─── Daten laden ─────────────────────────────────────────────────────────────
async function refresh() {
+ if (pendingDeletes.size > 0) return; // edit-Modus schützen
try {
const [programs, active] = await Promise.all([
apiFetch('GET', '/api/programs'),
@@ -303,6 +355,30 @@
}
}
+ // ─── Zeilen-Löschung: Abbrechen / Speichern ─────────────────────────────────
+ function cancelDeletes() {
+ pendingDeletes.clear();
+ if (cachedState) renderLines(cachedState);
+ else updateEditBar();
+ }
+
+ async function saveDeletes() {
+ const indices = [...pendingDeletes].sort((a, b) => b - a); // höchste zuerst → kein Index-Shift
+ setStatus(elActStatus, `Lösche ${indices.length} Zeile(n)…`);
+ document.getElementById('btn-save-delete').disabled = true;
+ try {
+ for (const i of indices) {
+ await apiFetch('DELETE', `/api/active/lines/${i}`);
+ }
+ pendingDeletes.clear();
+ setStatus(elActStatus, `${indices.length} Zeile(n) gelöscht`);
+ await refresh();
+ } catch (err) {
+ setStatus(elActStatus, `Fehler: ${err.message}`, true);
+ document.getElementById('btn-save-delete').disabled = false;
+ }
+ }
+
// ─── Programm löschen (aus Toolbar) ─────────────────────────────────────────
async function deleteSelected() {
if (!selectedId) return;
@@ -332,6 +408,8 @@
// ─── Event-Listener ──────────────────────────────────────────────────────────
document.getElementById('btn-refresh').addEventListener('click', refresh);
document.getElementById('btn-new-folder').addEventListener('click', createFolder);
+ document.getElementById('btn-cancel-delete').addEventListener('click', cancelDeletes);
+ document.getElementById('btn-save-delete').addEventListener('click', saveDeletes);
elBtnDeleteSelected.addEventListener('click', deleteSelected);
elBtnFirst.addEventListener('click', () => step('first'));
elBtnPrev .addEventListener('click', () => step('prev'));