import { computeLayout } from "./layout.js"; import { RepoRenderer } from "./renderer.js"; const landing = document.getElementById("landing"); const loading = document.getElementById("loading"); const loadingText = document.getElementById("loading-text"); const viewport = document.getElementById("viewport"); const controlsHint = document.getElementById("controls-hint"); const gitUrlInput = document.getElementById("git-url"); const btnClone = document.getElementById("btn-clone"); const dropZone = document.getElementById("drop-zone"); const fileInput = document.getElementById("file-input"); const historyEl = document.getElementById("history"); const historyList = document.getElementById("history-list"); function showLoading(msg) { landing.style.display = "none"; loading.classList.add("active"); loadingText.textContent = msg; } function showVisualization() { loading.classList.remove("active"); viewport.classList.add("active"); controlsHint.classList.add("active"); } function showError(msg) { loading.classList.remove("active"); landing.style.display = ""; alert(msg); } async function visualize(tree, repoName, cacheKey) { showLoading("Building layout..."); // Wait for fonts to load so canvas renders them correctly await document.fonts.ready; await new Promise((r) => setTimeout(r, 50)); const { leaves, totalWidth, totalHeight } = computeLayout(tree); if (leaves.length === 0) { showError("No source files found in repository."); return; } showLoading(`Rendering ${leaves.length} files...`); await new Promise((r) => setTimeout(r, 50)); showVisualization(); document.getElementById("osd-info").classList.add("active"); const renderer = new RepoRenderer(viewport, repoName || tree.name, cacheKey); await renderer.load(leaves, totalWidth, totalHeight); } // --- History --- async function loadHistory() { try { const res = await fetch("/api/repos"); if (!res.ok) return; const repos = await res.json(); if (repos.length === 0) return; historyEl.classList.add("has-items"); historyList.innerHTML = ""; for (const repo of repos) { const item = document.createElement("div"); item.className = "history-item"; item.innerHTML = ` ${escapeHtml(repo.name)} ${repo.file_count} files `; item.addEventListener("click", () => loadCachedRepo(repo.cache_key, repo.name)); historyList.appendChild(item); } } catch { // ignore } } async function loadCachedRepo(key, name) { showLoading(`Loading ${name}...`); try { const res = await fetch(`/api/repos/${key}`); if (!res.ok) throw new Error("Cache expired"); const { cache_key, tree } = await res.json(); await visualize(tree, name, cache_key); } catch (err) { showError(err.message); } } function escapeHtml(s) { const div = document.createElement("div"); div.textContent = s; return div.innerHTML; } // Load history on page load loadHistory(); // --- Git clone --- btnClone.addEventListener("click", async () => { const url = gitUrlInput.value.trim(); if (!url) return; btnClone.disabled = true; showLoading("Cloning repository..."); try { const res = await fetch("/api/scan-git", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ url }), }); if (!res.ok) { const err = await res.json(); throw new Error(err.error || "Clone failed"); } const { cache_key, tree } = await res.json(); await visualize(tree, undefined, cache_key); } catch (err) { showError(err.message); } finally { btnClone.disabled = false; } }); gitUrlInput.addEventListener("keydown", (e) => { if (e.key === "Enter") btnClone.click(); }); // --- Zip upload --- dropZone.addEventListener("click", () => fileInput.click()); dropZone.addEventListener("dragover", (e) => { e.preventDefault(); dropZone.classList.add("dragover"); }); dropZone.addEventListener("dragleave", () => { dropZone.classList.remove("dragover"); }); dropZone.addEventListener("drop", (e) => { e.preventDefault(); dropZone.classList.remove("dragover"); const file = e.dataTransfer.files[0]; if (file) uploadZip(file); }); fileInput.addEventListener("change", () => { if (fileInput.files[0]) uploadZip(fileInput.files[0]); }); async function uploadZip(file) { showLoading("Uploading and scanning zip..."); try { const form = new FormData(); form.append("file", file); const res = await fetch("/api/scan-zip", { method: "POST", body: form, }); if (!res.ok) { const err = await res.json(); throw new Error(err.error || "Upload failed"); } const { cache_key, tree } = await res.json(); await visualize(tree, undefined, cache_key); } catch (err) { showError(err.message); } }