FileBrowser Folder
This commit is contained in:
@@ -84,6 +84,21 @@ body {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* ===== BREADCRUMB ===== */
|
||||
.breadcrumb {
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
margin-bottom: 8px;
|
||||
min-height: 18px;
|
||||
}
|
||||
.bc-seg {
|
||||
cursor: pointer;
|
||||
color: var(--accent);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
.bc-seg:hover { color: var(--text); }
|
||||
|
||||
/* ===== PROGRAM LIST ===== */
|
||||
#program-list {
|
||||
display: flex;
|
||||
|
||||
@@ -22,8 +22,11 @@
|
||||
<span class="badge" id="prog-count">–</span>
|
||||
</h2>
|
||||
|
||||
<div id="breadcrumb" class="breadcrumb"></div>
|
||||
|
||||
<div class="controls">
|
||||
<button id="btn-refresh" title="Aktualisieren">⟳</button>
|
||||
<button id="btn-new-file" title="Neue Datei erstellen">📄+</button>
|
||||
<button id="btn-new-folder" title="Neuen Ordner erstellen">📁+</button>
|
||||
<button id="btn-delete-selected" title="Ausgewähltes löschen" disabled>🗑</button>
|
||||
</div>
|
||||
@@ -97,6 +100,8 @@
|
||||
let selectedType = null; // 'file' | 'folder'
|
||||
let pendingDeletes = new Set(); // Indices markierter Zeilen (noch nicht gespeichert)
|
||||
let cachedState = null; // letzter bekannter Serverzustand (für Abbrechen)
|
||||
let currentDir = ''; // aktuelles Verzeichnis (relativer Pfad, z. B. 'training/run1')
|
||||
let currentPath = []; // Segmente des currentDir als Array (für Breadcrumb)
|
||||
|
||||
// ─── DOM-Referenzen ───────────────────────────────────────────────────────────
|
||||
const elProgCount = document.getElementById('prog-count');
|
||||
@@ -190,11 +195,15 @@
|
||||
|
||||
elProgList.innerHTML = folderHtml + fileHtml;
|
||||
|
||||
// Klick → auswählen + bei Dateien sofort laden
|
||||
// Klick → auswählen; Datei laden; Ordner navigieren
|
||||
elProgList.querySelectorAll('.prog-item').forEach(el => {
|
||||
el.addEventListener('click', () => {
|
||||
setSelected(el.dataset.id, el.dataset.type);
|
||||
if (el.dataset.type === 'file') loadProgram(el.dataset.id);
|
||||
if (el.dataset.type === 'folder') {
|
||||
navigateInto(el.dataset.id);
|
||||
} else {
|
||||
setSelected(el.dataset.id, el.dataset.type);
|
||||
loadProgram(el.dataset.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -300,15 +309,47 @@
|
||||
updateEditBar();
|
||||
}
|
||||
|
||||
// ─── Breadcrumb ──────────────────────────────────────────────────────────────
|
||||
function renderBreadcrumb() {
|
||||
const el = document.getElementById('breadcrumb');
|
||||
let html = `<span class="bc-seg" data-depth="-1">GCodeFiles</span>`;
|
||||
currentPath.forEach((seg, i) => {
|
||||
html += ` / <span class="bc-seg" data-depth="${i}">${esc(seg)}</span>`;
|
||||
});
|
||||
el.innerHTML = html;
|
||||
el.querySelectorAll('.bc-seg').forEach(span => {
|
||||
span.addEventListener('click', () => {
|
||||
const depth = Number(span.dataset.depth);
|
||||
if (depth === -1) { currentPath = []; currentDir = ''; }
|
||||
else { currentPath = currentPath.slice(0, depth + 1); currentDir = currentPath.join('/'); }
|
||||
selectedId = null; selectedType = null;
|
||||
elBtnDeleteSelected.disabled = true;
|
||||
refresh();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ─── In Ordner navigieren ────────────────────────────────────────────────────
|
||||
function navigateInto(folderName) {
|
||||
currentPath.push(folderName);
|
||||
currentDir = currentPath.join('/');
|
||||
selectedId = null; selectedType = null;
|
||||
elBtnDeleteSelected.disabled = true;
|
||||
refresh();
|
||||
}
|
||||
|
||||
// ─── Daten laden ─────────────────────────────────────────────────────────────
|
||||
async function refresh() {
|
||||
if (pendingDeletes.size > 0) return; // edit-Modus schützen
|
||||
const dirQ = currentDir ? `?dir=${encodeURIComponent(currentDir)}` : '';
|
||||
try {
|
||||
const [programs, active] = await Promise.all([
|
||||
apiFetch('GET', '/api/programs'),
|
||||
const [programs, folders, active] = await Promise.all([
|
||||
apiFetch('GET', `/api/programs${dirQ}`),
|
||||
apiFetch('GET', `/api/folders${dirQ}`),
|
||||
apiFetch('GET', '/api/active'),
|
||||
]);
|
||||
renderProgList(programs.programs || [], [], active.programId);
|
||||
renderBreadcrumb();
|
||||
renderProgList(programs.programs || [], folders.folders || [], active.programId);
|
||||
renderLines(active);
|
||||
setStatus(elListStatus, '');
|
||||
setStatus(elActStatus, '');
|
||||
@@ -318,10 +359,10 @@
|
||||
}
|
||||
|
||||
// ─── Programm laden (FLoad-Äquivalent) ───────────────────────────────────────
|
||||
async function loadProgram(id) {
|
||||
async function loadProgram(id, dir) {
|
||||
setStatus(elActStatus, `Lade '${id}'…`);
|
||||
try {
|
||||
const state = await apiFetch('PUT', '/api/active', { id });
|
||||
const state = await apiFetch('PUT', '/api/active', { id, dir: dir ?? currentDir });
|
||||
renderLines(state);
|
||||
await refresh();
|
||||
setStatus(elActStatus, `'${id}' geladen`);
|
||||
@@ -379,22 +420,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ─── 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}'…`);
|
||||
// ─── Neue Datei anlegen ──────────────────────────────────────────────────────
|
||||
async function createFile() {
|
||||
const name = prompt('Name der neuen Datei (ohne .gcode):');
|
||||
if (!name || !name.trim()) return;
|
||||
setStatus(elListStatus, `Erstelle '${name.trim()}'…`);
|
||||
try {
|
||||
await apiFetch('DELETE', `/api/programs/${encodeURIComponent(selectedId)}`);
|
||||
setStatus(elListStatus, `'${selectedId}' gelöscht`);
|
||||
selectedId = null;
|
||||
selectedType = null;
|
||||
elBtnDeleteSelected.disabled = true;
|
||||
await refresh();
|
||||
const meta = await apiFetch('POST', '/api/programs', { name: name.trim(), dir: currentDir });
|
||||
setStatus(elListStatus, `'${meta.id}' erstellt`);
|
||||
await loadProgram(meta.id, currentDir);
|
||||
} catch (err) {
|
||||
setStatus(elListStatus, `Fehler: ${err.message}`, true);
|
||||
}
|
||||
@@ -402,11 +436,54 @@
|
||||
|
||||
// ─── Neuen Ordner anlegen ────────────────────────────────────────────────────
|
||||
async function createFolder() {
|
||||
alert('Ordner-Verwaltung ist noch nicht implementiert.\n(kommt in Phase 3 des Roadmaps)');
|
||||
const name = prompt('Name des neuen Ordners:');
|
||||
if (!name || !name.trim()) return;
|
||||
const slug = name.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, '_').replace(/^_+|_+$/g, '');
|
||||
if (!slug) { alert('Ungültiger Ordnername.'); return; }
|
||||
setStatus(elListStatus, `Erstelle Ordner '${slug}'…`);
|
||||
try {
|
||||
await apiFetch('POST', '/api/folders', { name: slug, dir: currentDir });
|
||||
setStatus(elListStatus, `Ordner '${slug}' erstellt`);
|
||||
await refresh();
|
||||
} catch (err) {
|
||||
setStatus(elListStatus, `Fehler: ${err.message}`, true);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Auswahl löschen (Toolbar-🗑) ────────────────────────────────────────────
|
||||
async function deleteSelected() {
|
||||
if (!selectedId) return;
|
||||
const dirQ = currentDir ? `?dir=${encodeURIComponent(currentDir)}` : '';
|
||||
if (selectedType === 'folder') {
|
||||
if (!confirm(`Ordner '${selectedId}' und seinen gesamten Inhalt wirklich löschen?`)) return;
|
||||
setStatus(elListStatus, `Lösche Ordner '${selectedId}'…`);
|
||||
try {
|
||||
await apiFetch('DELETE', `/api/folders/${encodeURIComponent(selectedId)}${dirQ}`);
|
||||
setStatus(elListStatus, `Ordner '${selectedId}' gelöscht`);
|
||||
selectedId = null; selectedType = null;
|
||||
elBtnDeleteSelected.disabled = true;
|
||||
await refresh();
|
||||
} catch (err) {
|
||||
setStatus(elListStatus, `Fehler: ${err.message}`, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!confirm(`Programm '${selectedId}' wirklich löschen?`)) return;
|
||||
setStatus(elListStatus, `Lösche '${selectedId}'…`);
|
||||
try {
|
||||
await apiFetch('DELETE', `/api/programs/${encodeURIComponent(selectedId)}${dirQ}`);
|
||||
setStatus(elListStatus, `'${selectedId}' gelöscht`);
|
||||
selectedId = null; selectedType = null;
|
||||
elBtnDeleteSelected.disabled = true;
|
||||
await refresh();
|
||||
} catch (err) {
|
||||
setStatus(elListStatus, `Fehler: ${err.message}`, true);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Event-Listener ──────────────────────────────────────────────────────────
|
||||
document.getElementById('btn-refresh').addEventListener('click', refresh);
|
||||
document.getElementById('btn-new-file').addEventListener('click', createFile);
|
||||
document.getElementById('btn-new-folder').addEventListener('click', createFolder);
|
||||
document.getElementById('btn-cancel-delete').addEventListener('click', cancelDeletes);
|
||||
document.getElementById('btn-save-delete').addEventListener('click', saveDeletes);
|
||||
|
||||
Reference in New Issue
Block a user