FileBrowser
This commit is contained in:
@@ -12,15 +12,20 @@
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
html, body {
|
||||
html {
|
||||
min-height: 100%;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
background: linear-gradient(to bottom, #dddddd -20%, var(--bg) 130%);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
body { padding: 16px; }
|
||||
body {
|
||||
min-height: 100vh;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
/* background-attachment: fixed hält den Verlauf relativ zum Viewport —
|
||||
verhindert das Wiederholen bei langen Seiten */
|
||||
background: linear-gradient(to bottom, #dddddd -20%, var(--bg) 130%) fixed;
|
||||
color: var(--text);
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
/* ===== HEADER ===== */
|
||||
.app-header {
|
||||
@@ -28,6 +33,10 @@ body { padding: 16px; }
|
||||
align-items: baseline;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 10px 16px;
|
||||
}
|
||||
.app-header h1 {
|
||||
font-size: 16px;
|
||||
@@ -100,6 +109,11 @@ body { padding: 16px; }
|
||||
background: var(--active);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.prog-item.is-selected {
|
||||
background: #1e293b;
|
||||
border-color: #475569;
|
||||
}
|
||||
.prog-item.is-folder { font-style: italic; }
|
||||
.prog-item .prog-name {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
|
||||
@@ -23,7 +23,9 @@
|
||||
</h2>
|
||||
|
||||
<div class="controls">
|
||||
<button id="btn-refresh" title="Liste neu laden">⟳ Aktualisieren</button>
|
||||
<button id="btn-refresh" title="Aktualisieren">⟳</button>
|
||||
<button id="btn-new-folder" title="Neuen Ordner erstellen">📁+</button>
|
||||
<button id="btn-delete-selected" title="Ausgewähltes löschen" disabled>🗑</button>
|
||||
</div>
|
||||
|
||||
<div class="status-line" id="list-status"></div>
|
||||
@@ -84,6 +86,8 @@
|
||||
|
||||
// ─── Zustand ─────────────────────────────────────────────────────────────────
|
||||
let currentActiveId = null;
|
||||
let selectedId = null; // in der Liste ausgewähltes Item (unabhängig vom aktiven Programm)
|
||||
let selectedType = null; // 'file' | 'folder'
|
||||
|
||||
// ─── DOM-Referenzen ───────────────────────────────────────────────────────────
|
||||
const elProgCount = document.getElementById('prog-count');
|
||||
@@ -103,6 +107,7 @@
|
||||
const elBtnLast = document.getElementById('btn-last');
|
||||
const elBtnClear = document.getElementById('btn-clear');
|
||||
const elBtnDownload = document.getElementById('btn-download');
|
||||
const elBtnDeleteSelected = document.getElementById('btn-delete-selected');
|
||||
|
||||
// ─── API-Helpers ─────────────────────────────────────────────────────────────
|
||||
async function apiFetch(method, path, body) {
|
||||
@@ -137,37 +142,50 @@
|
||||
return { code: line.slice(0, idx).trim(), ts: fmtTs(line.slice(idx + 1).trim()) };
|
||||
}
|
||||
|
||||
// ─── Selection-State aktualisieren ──────────────────────────────────────────
|
||||
function setSelected(id, type) {
|
||||
selectedId = id;
|
||||
selectedType = type;
|
||||
// Alle Items neu markieren
|
||||
elProgList.querySelectorAll('.prog-item').forEach(el => {
|
||||
el.classList.toggle('is-selected', el.dataset.id === id);
|
||||
});
|
||||
elBtnDeleteSelected.disabled = !id;
|
||||
}
|
||||
|
||||
// ─── Programmliste rendern ───────────────────────────────────────────────────
|
||||
function renderProgList(programs, activeId) {
|
||||
elProgCount.textContent = programs.length;
|
||||
if (!programs.length) {
|
||||
function renderProgList(programs, folders, activeId) {
|
||||
const total = folders.length + programs.length;
|
||||
elProgCount.textContent = total;
|
||||
if (!total) {
|
||||
elProgList.innerHTML = '<span class="empty-hint">Keine Programme gefunden</span>';
|
||||
elBtnDeleteSelected.disabled = true;
|
||||
return;
|
||||
}
|
||||
elProgList.innerHTML = programs
|
||||
.map(p => {
|
||||
|
||||
// Ordner zuerst, dann Dateien
|
||||
const folderHtml = folders.map(f => `
|
||||
<div class="prog-item is-folder" data-id="${esc(f.id)}" data-type="folder">
|
||||
<span class="prog-name" title="${esc(f.name)}">📁 ${esc(f.name)}</span>
|
||||
</div>`).join('');
|
||||
|
||||
const fileHtml = programs.map(p => {
|
||||
const isActive = p.id === activeId;
|
||||
return `<div class="prog-item${isActive ? ' is-active' : ''}" data-id="${esc(p.id)}">
|
||||
<span class="prog-name" title="${esc(p.name)}">${esc(p.name)}</span>
|
||||
const isSel = p.id === selectedId;
|
||||
return `<div class="prog-item${isActive ? ' is-active' : ''}${isSel ? ' is-selected' : ''}" data-id="${esc(p.id)}" data-type="file">
|
||||
<span class="prog-name" title="${esc(p.name)}">📄 ${esc(p.name)}</span>
|
||||
<span class="prog-id">${esc(p.id)}</span>
|
||||
<span class="prog-count">${p.lineCount} Z.</span>
|
||||
<button class="btn-del" data-id="${esc(p.id)}" title="Löschen">✕</button>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
// Klick auf Zeile → FLoad (Programm aktivieren)
|
||||
elProgList.querySelectorAll('.prog-item').forEach(el => {
|
||||
el.addEventListener('click', e => {
|
||||
if (e.target.classList.contains('btn-del')) return;
|
||||
loadProgram(el.dataset.id);
|
||||
});
|
||||
});
|
||||
elProgList.innerHTML = folderHtml + fileHtml;
|
||||
|
||||
// Klick auf Löschen-Button
|
||||
elProgList.querySelectorAll('.btn-del').forEach(btn => {
|
||||
btn.addEventListener('click', e => {
|
||||
e.stopPropagation();
|
||||
deleteProgram(btn.dataset.id);
|
||||
// Einfachklick → auswählen; Doppelklick → Programm laden
|
||||
elProgList.querySelectorAll('.prog-item').forEach(el => {
|
||||
el.addEventListener('click', () => setSelected(el.dataset.id, el.dataset.type));
|
||||
el.addEventListener('dblclick', () => {
|
||||
if (el.dataset.type === 'file') loadProgram(el.dataset.id);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -238,7 +256,7 @@
|
||||
apiFetch('GET', '/api/programs'),
|
||||
apiFetch('GET', '/api/active'),
|
||||
]);
|
||||
renderProgList(programs.programs || [], active.programId);
|
||||
renderProgList(programs.programs || [], [], active.programId);
|
||||
renderLines(active);
|
||||
setStatus(elListStatus, '');
|
||||
setStatus(elActStatus, '');
|
||||
@@ -260,19 +278,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Programm löschen ────────────────────────────────────────────────────────
|
||||
async function deleteProgram(id) {
|
||||
if (!confirm(`Programm '${id}' wirklich löschen?`)) return;
|
||||
setStatus(elListStatus, `Lösche '${id}'…`);
|
||||
try {
|
||||
await apiFetch('DELETE', `/api/programs/${encodeURIComponent(id)}`);
|
||||
setStatus(elListStatus, `'${id}' gelöscht`);
|
||||
await refresh();
|
||||
} catch (err) {
|
||||
setStatus(elListStatus, `Fehler: ${err.message}`, true);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Stepping ────────────────────────────────────────────────────────────────
|
||||
async function step(endpoint) {
|
||||
setStatus(elActStatus, '…');
|
||||
@@ -298,8 +303,36 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Programm löschen (aus Toolbar) ─────────────────────────────────────────
|
||||
async function deleteSelected() {
|
||||
if (!selectedId) return;
|
||||
if (selectedType === 'folder') {
|
||||
alert(`Ordner-Löschung ist noch nicht implementiert.\n('${selectedId}')`);
|
||||
return;
|
||||
}
|
||||
if (!confirm(`Programm '${selectedId}' wirklich löschen?`)) return;
|
||||
setStatus(elListStatus, `Lösche '${selectedId}'…`);
|
||||
try {
|
||||
await apiFetch('DELETE', `/api/programs/${encodeURIComponent(selectedId)}`);
|
||||
setStatus(elListStatus, `'${selectedId}' gelöscht`);
|
||||
selectedId = null;
|
||||
selectedType = null;
|
||||
elBtnDeleteSelected.disabled = true;
|
||||
await refresh();
|
||||
} catch (err) {
|
||||
setStatus(elListStatus, `Fehler: ${err.message}`, true);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Neuen Ordner anlegen ────────────────────────────────────────────────────
|
||||
async function createFolder() {
|
||||
alert('Ordner-Verwaltung ist noch nicht implementiert.\n(kommt in Phase 3 des Roadmaps)');
|
||||
}
|
||||
|
||||
// ─── Event-Listener ──────────────────────────────────────────────────────────
|
||||
document.getElementById('btn-refresh').addEventListener('click', refresh);
|
||||
document.getElementById('btn-new-folder').addEventListener('click', createFolder);
|
||||
elBtnDeleteSelected.addEventListener('click', deleteSelected);
|
||||
elBtnFirst.addEventListener('click', () => step('first'));
|
||||
elBtnPrev .addEventListener('click', () => step('prev'));
|
||||
elBtnNext .addEventListener('click', () => step('next'));
|
||||
|
||||
Reference in New Issue
Block a user