FileBrowser LineDelete
This commit is contained in:
@@ -246,6 +246,67 @@ table td.ts { color: var(--muted); font-size: 11px; }
|
|||||||
}
|
}
|
||||||
.status-line.err { color: #fca5a5; }
|
.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 ===== */
|
/* ===== DOWNLOAD LINK ===== */
|
||||||
.dl-link {
|
.dl-link {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|||||||
@@ -68,13 +68,20 @@
|
|||||||
<th class="idx">#</th>
|
<th class="idx">#</th>
|
||||||
<th class="line">G-Code (gespeichert in Grad)</th>
|
<th class="line">G-Code (gespeichert in Grad)</th>
|
||||||
<th class="ts">Zeitstempel</th>
|
<th class="ts">Zeitstempel</th>
|
||||||
|
<th class="del-col"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="lines-body">
|
<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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
</div><!-- .sections -->
|
</div><!-- .sections -->
|
||||||
@@ -85,9 +92,11 @@
|
|||||||
const REFRESH_MS = 5000; // Auto-Refresh-Intervall
|
const REFRESH_MS = 5000; // Auto-Refresh-Intervall
|
||||||
|
|
||||||
// ─── Zustand ─────────────────────────────────────────────────────────────────
|
// ─── Zustand ─────────────────────────────────────────────────────────────────
|
||||||
let currentActiveId = null;
|
let currentActiveId = null;
|
||||||
let selectedId = null; // in der Liste ausgewähltes Item (unabhängig vom aktiven Programm)
|
let selectedId = null; // in der Liste ausgewähltes Item (unabhängig vom aktiven Programm)
|
||||||
let selectedType = null; // 'file' | 'folder'
|
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 ───────────────────────────────────────────────────────────
|
// ─── DOM-Referenzen ───────────────────────────────────────────────────────────
|
||||||
const elProgCount = document.getElementById('prog-count');
|
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 ──────────────────────────────────────────────────
|
// ─── Zeilen-Tabelle rendern ──────────────────────────────────────────────────
|
||||||
function renderLines(state) {
|
function renderLines(state) {
|
||||||
|
cachedState = state;
|
||||||
const { programId, cursor, lineCount, lines = [], version } = state;
|
const { programId, cursor, lineCount, lines = [], version } = state;
|
||||||
currentActiveId = programId;
|
currentActiveId = programId;
|
||||||
|
|
||||||
@@ -228,29 +264,45 @@
|
|||||||
|
|
||||||
// Tabelle
|
// Tabelle
|
||||||
if (!lines.length) {
|
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'
|
hasProgram ? 'Programm ist leer' : 'Kein aktives Programm — links ein Programm anklicken'
|
||||||
}</td></tr>`;
|
}</td></tr>`;
|
||||||
|
updateEditBar();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
elLinesBody.innerHTML = lines.map((line, i) => {
|
elLinesBody.innerHTML = lines.map((line, i) => {
|
||||||
const { code, ts } = parseLine(line);
|
const { code, ts } = parseLine(line);
|
||||||
const isCursor = i === cursor;
|
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="idx">${i}</td>
|
||||||
<td class="line">${isCursor ? '▶ ' : ''}${esc(code)}</td>
|
<td class="line">${isCursor ? '▶ ' : ''}${esc(code)}</td>
|
||||||
<td class="ts">${esc(ts)}</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>`;
|
</tr>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
// Zur Cursor-Zeile scrollen
|
// Delete-Buttons verdrahten
|
||||||
const cursorRow = elLinesBody.querySelector('.cursor-row');
|
elLinesBody.querySelectorAll('.btn-row-del').forEach(btn => {
|
||||||
if (cursorRow) cursorRow.scrollIntoView({ block: 'nearest' });
|
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 ─────────────────────────────────────────────────────────────
|
// ─── Daten laden ─────────────────────────────────────────────────────────────
|
||||||
async function refresh() {
|
async function refresh() {
|
||||||
|
if (pendingDeletes.size > 0) return; // edit-Modus schützen
|
||||||
try {
|
try {
|
||||||
const [programs, active] = await Promise.all([
|
const [programs, active] = await Promise.all([
|
||||||
apiFetch('GET', '/api/programs'),
|
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) ─────────────────────────────────────────
|
// ─── Programm löschen (aus Toolbar) ─────────────────────────────────────────
|
||||||
async function deleteSelected() {
|
async function deleteSelected() {
|
||||||
if (!selectedId) return;
|
if (!selectedId) return;
|
||||||
@@ -332,6 +408,8 @@
|
|||||||
// ─── Event-Listener ──────────────────────────────────────────────────────────
|
// ─── Event-Listener ──────────────────────────────────────────────────────────
|
||||||
document.getElementById('btn-refresh').addEventListener('click', refresh);
|
document.getElementById('btn-refresh').addEventListener('click', refresh);
|
||||||
document.getElementById('btn-new-folder').addEventListener('click', createFolder);
|
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);
|
elBtnDeleteSelected.addEventListener('click', deleteSelected);
|
||||||
elBtnFirst.addEventListener('click', () => step('first'));
|
elBtnFirst.addEventListener('click', () => step('first'));
|
||||||
elBtnPrev .addEventListener('click', () => step('prev'));
|
elBtnPrev .addEventListener('click', () => step('prev'));
|
||||||
|
|||||||
Reference in New Issue
Block a user