Spaces:
Sleeping
Sleeping
| const testerInput = document.getElementById("tester"); | |
| const samplerInput = document.getElementById("samplers"); | |
| const actionContainer = document.getElementById("action-container"); | |
| const resultsSection = document.getElementById("results"); | |
| let canvas = document.getElementById("lines-canvas"); | |
| let ctx = canvas.getContext("2d"); | |
| let generateBtn, loaderDiv; | |
| function resetUI() { | |
| resultsSection.classList.add("hidden"); | |
| resultsSection.innerHTML = ` | |
| <canvas id="lines-canvas"></canvas> | |
| <div id="result-layout"> | |
| <div id="tester-column"></div> | |
| <div id="sampler-column"></div> | |
| </div> | |
| `; | |
| // Re-attach canvas & ctx properly | |
| canvas = document.getElementById("lines-canvas"); | |
| ctx = canvas.getContext("2d"); | |
| canvas.width = 0; | |
| canvas.height = 0; | |
| // Remove old button & loader if exists | |
| if (loaderDiv) loaderDiv.remove(); | |
| if (generateBtn) generateBtn.remove(); | |
| } | |
| testerInput.addEventListener("change", handleFileChange); | |
| samplerInput.addEventListener("change", handleFileChange); | |
| function handleFileChange() { | |
| resetUI(); | |
| const testerReady = testerInput.files.length === 1; | |
| const samplersReady = | |
| samplerInput.files.length > 0 && samplerInput.files.length <= 5; | |
| if (samplerInput.files.length > 5) { | |
| alert("You can upload a maximum of 5 sampler photos."); | |
| samplerInput.value = ""; | |
| return; | |
| } | |
| if (testerReady && samplersReady) { | |
| generateBtn = document.createElement("button"); | |
| generateBtn.textContent = "Generate Comparison"; | |
| generateBtn.className = "generate-btn"; | |
| generateBtn.onclick = sendComparison; | |
| actionContainer.appendChild(generateBtn); | |
| } | |
| } | |
| function showLoader() { | |
| loaderDiv = document.createElement("div"); | |
| loaderDiv.className = "loader"; | |
| actionContainer.appendChild(loaderDiv); | |
| } | |
| async function sendComparison() { | |
| generateBtn.disabled = true; | |
| showLoader(); | |
| const formData = new FormData(); | |
| formData.append("tester", testerInput.files[0]); | |
| for (let file of samplerInput.files) formData.append("samplers", file); | |
| try { | |
| const res = await fetch("/compare", { method: "POST", body: formData }); | |
| if (!res.ok) throw new Error(await res.text()); | |
| const data = await res.json(); | |
| renderResults(data); | |
| } catch (err) { | |
| alert("Error: " + err.message); | |
| } finally { | |
| generateBtn.disabled = false; | |
| loaderDiv.remove(); | |
| } | |
| } | |
| function getBorderColor(percent) { | |
| if (percent < 30) return "#ef4444"; // red | |
| if (percent <= 50) return "#fb923c"; // orange | |
| if (percent <= 80) return "#22c55e"; // green | |
| return "#a855f7"; // purple | |
| } | |
| function renderResults({ tester, results }) { | |
| resultsSection.classList.remove("hidden"); | |
| const testerColumn = document.getElementById("tester-column"); | |
| const samplerColumn = document.getElementById("sampler-column"); | |
| const testerSquare = createSquare(testerInput.files[0], "#3b82f6"); | |
| testerSquare.classList.add("tester-node"); | |
| testerColumn.appendChild(testerSquare); | |
| // Append results | |
| results.forEach((r,idx)=>{ | |
| const square = createSquare( | |
| samplerInput.files[idx], | |
| getBorderColor(r.similarity) | |
| ); | |
| samplerColumn.appendChild(square); | |
| /* Wait for browser calculate layout then draw line */ | |
| requestAnimationFrame(()=>drawLineBetween(testerSquare,square,r.similarity)); | |
| }); | |
| } | |
| function createSquare(file, borderColor) { | |
| const url = URL.createObjectURL(file); | |
| const div = document.createElement("div"); | |
| div.className = "square"; | |
| div.style.border = `4px solid ${borderColor}`; | |
| const img = document.createElement("img"); | |
| img.src = url; | |
| div.appendChild(img); | |
| return div; | |
| } | |
| function drawLineBetween(el1, el2, similarity) { | |
| const rect1 = el1.getBoundingClientRect(); | |
| const rect2 = el2.getBoundingClientRect(); | |
| const offset = resultsSection.getBoundingClientRect(); | |
| // Compute canvas size on first call | |
| if (canvas.width === 0) { | |
| canvas.width = resultsSection.scrollWidth; | |
| canvas.height = resultsSection.getBoundingClientRect().height; | |
| } | |
| const x1 = rect1.right - offset.left; | |
| const y1 = rect1.top + rect1.height / 2 - offset.top; | |
| const x2 = rect2.left - offset.left; | |
| const y2 = rect2.top + rect2.height / 2 - offset.top; | |
| ctx.strokeStyle = getBorderColor(similarity); | |
| ctx.lineWidth = 3; | |
| ctx.beginPath(); | |
| ctx.moveTo(x1, y1); | |
| ctx.lineTo(x2, y2); | |
| ctx.stroke(); | |
| // Label | |
| const label = document.createElement("div"); | |
| label.className = "label"; | |
| label.textContent = similarity.toFixed(0) + "%"; | |
| label.style.left = (x1 + x2) / 2 + "px"; | |
| label.style.top = (y1 + y2) / 2 + "px"; | |
| resultsSection.appendChild(label); | |
| } |