diff --git a/Endpoints/WorkspaceEndpoint.cs b/Endpoints/WorkspaceEndpoint.cs index 092d5a8..421038d 100644 --- a/Endpoints/WorkspaceEndpoint.cs +++ b/Endpoints/WorkspaceEndpoint.cs @@ -38,6 +38,7 @@ public static class WorkspaceEndpoint { app.MapGet("/api/workspace/tree", HandleTree); app.MapGet("/api/workspace/file", HandleFile); + app.MapGet("/api/workspace/browse", HandleBrowse); } private static async Task HandleTree(HttpContext ctx) @@ -157,6 +158,41 @@ public static class WorkspaceEndpoint }); } + private static async Task HandleBrowse(HttpContext ctx) + { + var path = ctx.Request.Query["path"].FirstOrDefault() ?? "/"; + + var targetPath = Path.GetFullPath(path); + if (!Directory.Exists(targetPath)) + { + ctx.Response.StatusCode = 404; + await ctx.Response.WriteAsJsonAsync(new { error = "Verzeichnis nicht gefunden" }); + return; + } + + var entries = new List(); + + // Parent + var parent = Directory.GetParent(targetPath); + if (parent != null) + { + entries.Add(new { name = "..", path = parent.FullName.Replace('\\', '/'), type = "dir" }); + } + + try + { + foreach (var dir in Directory.GetDirectories(targetPath).OrderBy(d => Path.GetFileName(d), StringComparer.OrdinalIgnoreCase)) + { + var name = Path.GetFileName(dir); + if (IgnoredDirs.Contains(name)) continue; + entries.Add(new { name, path = dir.Replace('\\', '/'), type = "dir" }); + } + } + catch (UnauthorizedAccessException) { } + + await ctx.Response.WriteAsJsonAsync(new { current = targetPath.Replace('\\', '/'), entries }); + } + private static bool IsLikelyTextFile(string name) { var lower = name.ToLowerInvariant(); diff --git a/wwwroot/css/file-explorer.css b/wwwroot/css/file-explorer.css index 08b954e..a298fd3 100644 --- a/wwwroot/css/file-explorer.css +++ b/wwwroot/css/file-explorer.css @@ -46,16 +46,7 @@ } .fe-btn { - background: var(--f3-bg); - border: var(--border-width) solid var(--f3-border); padding: var(--s1) var(--s2); - cursor: pointer; - color: var(--text); - font-family: monospace; -} - -.fe-btn:hover:not(:disabled) { - border-color: var(--primary); } .fe-btn:disabled { @@ -78,6 +69,10 @@ flex-shrink: 0; } +.fe-sidebar-full { + width: 100%; +} + .fe-main { flex: 1; overflow: hidden; @@ -118,10 +113,6 @@ margin: auto; } -.fe-error { - color: #ff6b6b; - opacity: 1; -} /* Preview */ @@ -200,12 +191,3 @@ flex: 1; } -.fe-select-btn { - background: var(--primary); - border-color: var(--primary); - white-space: nowrap; -} - -.fe-select-btn:hover { - opacity: 0.85; -} diff --git a/wwwroot/css/form.css b/wwwroot/css/form.css index b680b94..0f50ca8 100644 --- a/wwwroot/css/form.css +++ b/wwwroot/css/form.css @@ -32,6 +32,20 @@ object-fit: cover; } +.input-with-btn { + display: flex; + gap: var(--s2); +} + +.input-with-btn input { + flex: 1; +} + +.input-addon-btn { + flex-shrink: 0; + font-size: 1.1em; +} + .form-actions { display: flex; gap: var(--s4); diff --git a/wwwroot/index.html b/wwwroot/index.html index 14d38b8..f15d885 100644 --- a/wwwroot/index.html +++ b/wwwroot/index.html @@ -69,7 +69,10 @@ - +
+ + +
diff --git a/wwwroot/js/file-explorer.js b/wwwroot/js/file-explorer.js index f3fa62d..114be45 100644 --- a/wwwroot/js/file-explorer.js +++ b/wwwroot/js/file-explorer.js @@ -258,6 +258,114 @@ function feDoSelect() { closeFileExplorer(); } +// ── Folder Browser Mode ── +// Nutzt /api/workspace/browse mit absolutem Pfad, zum Ordner-Auswählen. + +function openFolderBrowser(opts = {}) { + if (_feState) closeFileExplorer(); + + _feState = { + mode: 'folder', + onSelect: opts.onSelect || null, + currentAbsPath: opts.startPath || '/', + history: [] + }; + + const overlay = document.createElement('div'); + overlay.id = 'feOverlay'; + overlay.className = 'fe-overlay'; + overlay.addEventListener('click', (e) => { if (e.target === overlay) closeFileExplorer(); }); + + const modal = document.createElement('div'); + modal.className = 'fe-modal'; + modal.innerHTML = ` +
+
+ + / +
+ +
+
+
+
Laden...
+
+
+ + `; + + overlay.appendChild(modal); + document.body.appendChild(overlay); + + document.getElementById('feBack').addEventListener('click', fbGoBack); + document.getElementById('feClose').addEventListener('click', closeFileExplorer); + document.getElementById('feSelectBtn').addEventListener('click', () => { + if (_feState && _feState.onSelect) _feState.onSelect(_feState.currentAbsPath); + closeFileExplorer(); + }); + + _feState._keyHandler = (e) => { if (e.key === 'Escape') closeFileExplorer(); }; + document.addEventListener('keydown', _feState._keyHandler); + + fbLoadDir(opts.startPath || '/'); +} + +async function fbLoadDir(absPath) { + if (!_feState) return; + + const pathEl = document.getElementById('fePath'); + const sidebar = document.getElementById('feSidebar'); + const info = document.getElementById('feInfo'); + + pathEl.textContent = absPath; + info.textContent = absPath; + sidebar.innerHTML = '
Laden...
'; + + try { + const resp = await fetch(`/api/workspace/browse?path=${encodeURIComponent(absPath)}`); + if (!resp.ok) { + const err = await resp.json().catch(() => ({})); + sidebar.innerHTML = `
${err.error || 'Fehler'}
`; + return; + } + + const data = await resp.json(); + _feState.currentAbsPath = data.current; + pathEl.textContent = data.current; + info.textContent = data.current; + + sidebar.innerHTML = ''; + + for (const entry of data.entries) { + const item = document.createElement('div'); + item.className = 'fe-item fe-dir'; + item.textContent = '📁 ' + entry.name; + item.addEventListener('click', () => { + _feState.history.push(_feState.currentAbsPath); + fbLoadDir(entry.path); + }); + sidebar.appendChild(item); + } + + if (data.entries.length === 0) { + sidebar.innerHTML = '
Leer
'; + } + + document.getElementById('feBack').disabled = _feState.history.length === 0; + } catch (e) { + sidebar.innerHTML = '
Netzwerk-Fehler
'; + } +} + +function fbGoBack() { + if (!_feState || _feState.history.length === 0) return; + const prev = _feState.history.pop(); + fbLoadDir(prev); +} + // ── Helpers ── function getFileIcon(ext) { diff --git a/wwwroot/js/pages/session-page.js b/wwwroot/js/pages/session-page.js index eb07921..4cb5440 100644 --- a/wwwroot/js/pages/session-page.js +++ b/wwwroot/js/pages/session-page.js @@ -79,6 +79,19 @@ registerPage('session', { imageInput.value = ''; }); + // Workspace Browse + const workspaceBrowse = document.getElementById('sessionWorkspaceBrowse'); + if (workspaceBrowse) { + workspaceBrowse.addEventListener('click', () => { + openFolderBrowser({ + startPath: workspaceEl.value.trim() || '/', + onSelect(path) { + workspaceEl.value = path; + } + }); + }); + } + // Speichern saveBtn.addEventListener('click', async () => { const data = {