From d46e03052145496e42859747f65eb093a9a3508f Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 17 Jan 2026 20:28:13 +0000 Subject: [PATCH] feat(ux): add loading state to generate button Replaces the full-screen loading overlay with an in-button spinner on the "Generate Poster" button. This provides a more modern, less disruptive user experience by giving feedback directly at the point of interaction. The button is now disabled during generation to prevent multiple submissions, and a dynamic aria-label has been added to inform screen reader users about the loading status. --- web_app/static/index.html | 5 +++- web_app/static/script.js | 49 ++++++++++++++++++++++++++------------- 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/web_app/static/index.html b/web_app/static/index.html index 0d56371..96ab59c 100644 --- a/web_app/static/index.html +++ b/web_app/static/index.html @@ -116,7 +116,10 @@

Lyrics

- + diff --git a/web_app/static/script.js b/web_app/static/script.js index 441a4ed..6549ebd 100644 --- a/web_app/static/script.js +++ b/web_app/static/script.js @@ -613,7 +613,31 @@ function handleLyricLineClick(lineNumber) { async function generatePoster() { if (!currentMetadata) return; - loadingOverlay.style.display = 'flex'; + const btnText = generateBtn.querySelector('.btn-text'); + const spinner = generateBtn.querySelector('.spinner'); + + const resetGenerateButton = () => { + generateBtn.disabled = false; + generateBtn.removeAttribute('aria-label'); + if (btnText) btnText.style.display = 'inline-block'; + if (spinner) spinner.style.display = 'none'; + }; + + // Validate before setting loading state + if (currentMetadata.type === 'track') { + const startVal = parseInt(startLineInput.value) || 1; + const endVal = parseInt(endLineInput.value) || 1; + if (isNaN(startVal) || isNaN(endVal) || startVal >= endVal) { + showToast("Please enter a valid range (Start must be less than End).", "error"); + return; + } + } + + // Set loading state + generateBtn.disabled = true; + generateBtn.setAttribute('aria-label', 'Generating poster, please wait.'); + if (btnText) btnText.style.display = 'none'; + if (spinner) spinner.style.display = 'inline-block'; const indexingToggle = document.getElementById('indexingToggle'); const accentToggle = document.getElementById('accentToggle'); @@ -628,23 +652,12 @@ async function generatePoster() { }; if (payload.type === 'track') { - // Lyrics Logic (Same as before) const startVal = parseInt(startLineInput.value) || 1; const endVal = parseInt(endLineInput.value) || 1; - const start = startVal - 1; - const end = endVal; - - if (isNaN(start) || isNaN(end) || start >= end) { - loadingOverlay.style.display = 'none'; - showToast("Please enter a valid range (Start must be less than End).", "error"); - return; - } - - payload.lyrics_start = start; - payload.lyrics_end = end; + payload.lyrics_start = startVal - 1; + payload.lyrics_end = endVal; } else { - // Album Logic payload.indexing = indexingToggle ? indexingToggle.checked : false; } @@ -669,13 +682,17 @@ async function generatePoster() { posterContainer.innerHTML = ''; posterContainer.appendChild(img); showDownloadButton(imageUrl, data.filename); - loadingOverlay.style.display = 'none'; + resetGenerateButton(); + }; + img.onerror = () => { + showToast('Failed to load the generated poster image.', 'error'); + resetGenerateButton(); }; } catch (error) { console.error("Generation failed", error); showToast(`Error: ${error.message}`, "error"); - loadingOverlay.style.display = 'none'; + resetGenerateButton(); } }