diff --git a/app/main.py b/app/main.py index ad1b855..f1b6acb 100644 --- a/app/main.py +++ b/app/main.py @@ -1113,6 +1113,65 @@ def update_settings(req: UpdateSettingsRequest): save_settings(settings) return {"status": "saved", "settings": settings} + +class CreateDirRequest(BaseModel): + parent_path: str + name: str + +@app.get("/api/system/browse-dir") +def browse_dir(path: str = ""): + """Browse directories on the system""" + if not path: + path = os.path.expanduser("~") + + path = os.path.abspath(path) + + if not os.path.exists(path): + raise HTTPException(status_code=404, detail="Path does not exist") + + if not os.path.isdir(path): + raise HTTPException(status_code=400, detail="Path is not a directory") + + try: + entries = [] + # Add parent directory entry if not at root + parent = os.path.dirname(path) + if parent != path: + entries.append({ + "name": "..", + "path": parent, + "is_dir": True + }) + + for name in sorted(os.listdir(path)): + full_path = os.path.join(path, name) + try: + if os.path.isdir(full_path): + entries.append({ + "name": name, + "path": full_path, + "is_dir": True + }) + except PermissionError: + continue + + return { + "current_path": path, + "entries": entries + } + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + +@app.post("/api/system/create-dir") +def create_dir(req: CreateDirRequest): + """Create a new directory""" + full_path = os.path.abspath(os.path.join(req.parent_path, req.name)) + try: + os.makedirs(full_path, exist_ok=True) + return {"status": "ok", "path": full_path} + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + @app.get("/api/uploads/{filename}") def get_upload_file(filename: str): file_path = os.path.join(upscaler.UPLOAD_DIR, filename) diff --git a/static/app.js b/static/app.js index 93058c8..a5a055d 100644 --- a/static/app.js +++ b/static/app.js @@ -2171,6 +2171,220 @@ async function initializeApp() { } }); } + + // Wire up folder browser modals + const browseJobTempBtn = document.getElementById("browse-job-temp-btn"); + const browseDefaultTempBtn = document.getElementById("browse-default-temp-btn"); + const folderModalCloseBtn = document.getElementById("folder-modal-close-btn"); + const folderCancelBtn = document.getElementById("folder-cancel-btn"); + const folderSelectBtn = document.getElementById("folder-select-btn"); + const folderUpBtn = document.getElementById("folder-up-btn"); + const createFolderBtn = document.getElementById("create-folder-btn"); + const newFolderNameInput = document.getElementById("new-folder-name"); + + if (browseJobTempBtn) { + browseJobTempBtn.addEventListener("click", () => { + openFolderBrowser(document.getElementById("temp-dir-input")); + }); + } + + if (browseDefaultTempBtn) { + browseDefaultTempBtn.addEventListener("click", () => { + openFolderBrowser(document.getElementById("default-temp-dir-input")); + }); + } + + if (folderModalCloseBtn) { + folderModalCloseBtn.addEventListener("click", closeFolderBrowser); + } + + if (folderCancelBtn) { + folderCancelBtn.addEventListener("click", closeFolderBrowser); + } + + // Close modal when clicking outside card + const folderModal = document.getElementById("folder-modal"); + if (folderModal) { + folderModal.addEventListener("click", (e) => { + if (e.target === folderModal) { + closeFolderBrowser(); + } + }); + } + + if (folderSelectBtn) { + folderSelectBtn.addEventListener("click", () => { + if (activeTargetInput && currentBrowsePath) { + activeTargetInput.value = currentBrowsePath; + activeTargetInput.dispatchEvent(new Event("change")); + activeTargetInput.dispatchEvent(new Event("input")); + } + closeFolderBrowser(); + }); + } + + if (folderUpBtn) { + folderUpBtn.addEventListener("click", () => { + if (currentBrowsePath) { + const parts = currentBrowsePath.split("/"); + if (parts.length > 1) { + parts.pop(); + const parent = parts.join("/") || "/"; + loadFolderDir(parent); + } + } + }); + } + + if (createFolderBtn) { + createFolderBtn.addEventListener("click", createFolder); + } + + if (newFolderNameInput) { + newFolderNameInput.addEventListener("keydown", (e) => { + if (e.key === "Enter") { + createFolder(); + } + }); + } +} + +/* ---------------------------------------------------- + Folder Browser Modal & API Helpers + ---------------------------------------------------- */ +let activeTargetInput = null; +let currentBrowsePath = ""; + +function openFolderBrowser(targetInput) { + activeTargetInput = targetInput; + const modal = document.getElementById("folder-modal"); + if (!modal) return; + + const statusDiv = document.getElementById("folder-modal-status"); + if (statusDiv) statusDiv.textContent = ""; + + let initialPath = targetInput ? targetInput.value.trim() : ""; + + modal.style.display = "flex"; + document.body.style.overflow = "hidden"; + + loadFolderDir(initialPath); +} + +function closeFolderBrowser() { + const modal = document.getElementById("folder-modal"); + if (modal) { + modal.style.display = "none"; + document.body.style.overflow = ""; + } + activeTargetInput = null; +} + +async function loadFolderDir(path) { + const listContainer = document.getElementById("folder-list"); + const currentPathInput = document.getElementById("folder-current-path"); + const statusDiv = document.getElementById("folder-modal-status"); + if (!listContainer) return; + + listContainer.innerHTML = '
Loading...
'; + + try { + const url = `/api/system/browse-dir?path=${encodeURIComponent(path)}`; + const res = await fetch(url); + if (!res.ok) { + const err = await res.json().catch(() => ({})); + throw new Error(err.detail || "Failed to load directory."); + } + + const data = await res.json(); + currentBrowsePath = data.current_path; + if (currentPathInput) currentPathInput.value = currentBrowsePath; + + listContainer.innerHTML = ""; + const entries = data.entries || []; + + if (entries.length === 0) { + listContainer.innerHTML = '
Empty directory
'; + return; + } + + entries.forEach(entry => { + const row = document.createElement("div"); + row.style.display = "flex"; + row.style.alignItems = "center"; + row.style.gap = "0.75rem"; + row.style.padding = "0.6rem 0.85rem"; + row.style.borderRadius = "6px"; + row.style.cursor = "pointer"; + row.style.transition = "background 0.2s"; + + row.addEventListener("mouseenter", () => { + row.style.background = "rgba(255, 255, 255, 0.05)"; + }); + row.addEventListener("mouseleave", () => { + row.style.background = ""; + }); + + const icon = document.createElement("i"); + if (entry.name === "..") { + icon.className = "fa-solid fa-arrow-turn-up"; + icon.style.color = "var(--text-muted)"; + icon.style.transform = "rotate(-90deg)"; + } else { + icon.className = "fa-solid fa-folder"; + icon.style.color = "var(--primary)"; + } + row.appendChild(icon); + + const label = document.createElement("span"); + label.textContent = entry.name; + label.style.fontSize = "0.9rem"; + label.style.fontFamily = "monospace"; + row.appendChild(label); + + row.addEventListener("click", () => { + loadFolderDir(entry.path); + }); + + listContainer.appendChild(row); + }); + + } catch (err) { + listContainer.innerHTML = `
Error: ${err.message}
`; + if (statusDiv) statusDiv.textContent = err.message; + } +} + +async function createFolder() { + const nameInput = document.getElementById("new-folder-name"); + const statusDiv = document.getElementById("folder-modal-status"); + if (!nameInput || !nameInput.value.trim() || !currentBrowsePath) return; + + const folderName = nameInput.value.trim(); + if (statusDiv) statusDiv.textContent = ""; + + try { + const res = await fetch("/api/system/create-dir", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + parent_path: currentBrowsePath, + name: folderName + }) + }); + + if (res.ok) { + nameInput.value = ""; + const data = await res.json(); + loadFolderDir(data.path); + } else { + const err = await res.json().catch(() => ({})); + if (statusDiv) statusDiv.textContent = err.detail || "Failed to create folder."; + } + } catch (err) { + console.error(err); + if (statusDiv) statusDiv.textContent = "Network error creating folder."; + } } async function loadTempInfo() { diff --git a/static/index.html b/static/index.html index 1d39721..ca16c67 100644 --- a/static/index.html +++ b/static/index.html @@ -279,8 +279,13 @@
- -

+

+ + +
+

Specify a custom location for temporary image frames to prevent running out of primary drive space.

@@ -594,6 +599,9 @@ + @@ -724,6 +732,42 @@ + + +