| 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> |
| `; |
|
|
| |
| canvas = document.getElementById("lines-canvas"); |
| ctx = canvas.getContext("2d"); |
| canvas.width = 0; |
| canvas.height = 0; |
|
|
| |
| 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"; |
| if (percent <= 50) return "#fb923c"; |
| if (percent <= 80) return "#22c55e"; |
| return "#a855f7"; |
| } |
|
|
| 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); |
| |
| results.forEach((r,idx)=>{ |
| const square = createSquare( |
| samplerInput.files[idx], |
| getBorderColor(r.similarity) |
| ); |
| samplerColumn.appendChild(square); |
| |
| 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(); |
|
|
| |
| 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(); |
|
|
| |
| 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); |
| } |