FileBrowser LineDelete

This commit is contained in:
chk
2026-06-14 22:29:49 +02:00
parent ed19c531a8
commit ed171baea1
2 changed files with 149 additions and 10 deletions

View File

@@ -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;

View File

@@ -68,13 +68,20 @@
<th class="idx">#</th>
<th class="line">G-Code (gespeichert in Grad)</th>
<th class="ts">Zeitstempel</th>
<th class="del-col"></th>
</tr>
</thead>
<tbody id="lines-body">
<tr><td colspan="3" class="empty-hint" style="padding:10px">Kein aktives Programm</td></tr>
<tr><td colspan="4" class="empty-hint" style="padding:10px">Kein aktives Programm</td></tr>
</tbody>
</table>
</div>
<div id="edit-bar" class="edit-bar" style="display:none">
<button id="btn-cancel-delete">✗ Abbrechen</button>
<button id="btn-save-delete" class="danger-confirm">✓ Speichern</button>
<span id="edit-bar-info" class="edit-bar-info"></span>
</div>
</div>
</div><!-- .sections -->
@@ -88,6 +95,8 @@
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 = `<tr><td colspan="3" class="empty-hint" style="padding:10px">${
elLinesBody.innerHTML = `<tr><td colspan="4" class="empty-hint" style="padding:10px">${
hasProgram ? 'Programm ist leer' : 'Kein aktives Programm — links ein Programm anklicken'
}</td></tr>`;
updateEditBar();
return;
}
elLinesBody.innerHTML = lines.map((line, i) => {
const { code, ts } = parseLine(line);
const isCursor = i === cursor;
return `<tr class="${isCursor ? 'cursor-row' : ''}">
const isPending = pendingDeletes.has(i);
return `<tr class="${isCursor ? 'cursor-row' : ''}${isPending ? ' pending-delete' : ''}" data-index="${i}">
<td class="idx">${i}</td>
<td class="line">${isCursor ? '▶ ' : ''}${esc(code)}</td>
<td class="ts">${esc(ts)}</td>
<td class="del-col"><button class="btn-row-del" data-index="${i}" title="Zeile löschen">🗑</button></td>
</tr>`;
}).join('');
// Zur Cursor-Zeile scrollen
// 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'));