Feat: Enhance download experience with progress indicators and toast notifications

This commit is contained in:
MasterAcnolo
2026-04-12 12:04:05 +02:00
parent 5aebc78d44
commit 011e0a703a
11 changed files with 329 additions and 31 deletions

View File

@@ -7,12 +7,24 @@ let userStoppedDownload = false;
form.addEventListener("submit", async (e) => {
e.preventDefault();
const urlInput = form.querySelector("input[name='url']");
const url = urlInput.value.trim();
// Basic URL validation
if (!url) {
window.showError("Please enter a URL");
return;
}
button.disabled = true;
stopBtn.style.display = "inline-flex";
statusDiv.style.display = "none"; // Hide status div
statusDiv.style.display = "none";
isDownloading = true;
userStoppedDownload = false;
// Show progress UI immediately with initial message
showInitialProgress();
const formData = new FormData(form);
const params = new URLSearchParams(formData);
@@ -26,12 +38,15 @@ form.addEventListener("submit", async (e) => {
if (!res.ok) {
if (!userStoppedDownload) {
const errorText = await res.text();
statusDiv.textContent = errorText;
statusDiv.style.display = "block";
window.showError(errorText);
resetProgressBar();
}
return;
}
// Download successful
window.showSuccess("Download completed!");
} catch (err) {
if (!isDownloading && !userStoppedDownload) {
statusDiv.style.display = "none";
@@ -43,6 +58,29 @@ form.addEventListener("submit", async (e) => {
}
});
function showInitialProgress() {
const progressWrapper = document.getElementById("downloadProgressWrapper");
const progressBarText = document.getElementById("downloadProgressText");
const speedElement = document.getElementById("downloadSpeedText");
const stageElement = document.getElementById("downloadStage");
const stopBtn = document.getElementById("stopBtn");
if (progressWrapper) progressWrapper.style.display = "block";
if (progressBarText) {
progressBarText.style.display = "block";
progressBarText.innerHTML = "0%";
}
if (speedElement) {
speedElement.style.display = "block";
speedElement.textContent = "0 Mib/s";
}
if (stageElement) {
stageElement.style.display = "block";
stageElement.textContent = "Retrieving data...";
}
if (stopBtn) stopBtn.style.display = "inline-flex";
}
async function stopDownload() {
if (!isDownloading) return;
@@ -56,6 +94,7 @@ async function stopDownload() {
headers: { "Content-Type": "application/x-www-form-urlencoded" },
});
window.showWarning("Download stopped");
} catch (err) {
console.error("Error stopping download:", err);
} finally {
@@ -63,12 +102,20 @@ async function stopDownload() {
button.disabled = false;
stopBtn.disabled = false;
stopBtn.style.display = "none";
// Reset progress bar
const progressWrapper = document.getElementById("downloadProgressWrapper");
if (progressWrapper) progressWrapper.style.display = "none";
const progressBar = document.getElementById("downloadProgress");
if (progressBar) progressBar.style.width = "0%";
resetProgressBar();
}
}
function resetProgressBar() {
const progressWrapper = document.getElementById("downloadProgressWrapper");
if (progressWrapper) progressWrapper.style.display = "none";
const progressBar = document.getElementById("downloadProgress");
if (progressBar) progressBar.style.width = "0%";
const progressBarText = document.getElementById("downloadProgressText");
if (progressBarText) progressBarText.style.display = "none";
const speedElement = document.getElementById("downloadSpeedText");
if (speedElement) speedElement.style.display = "none";
}

View File

@@ -3,23 +3,32 @@ const progressBar = document.getElementById("downloadProgress");
const progressBarText = document.getElementById("downloadProgressText")
const speedElement = document.getElementById("downloadSpeedText");
const stageElement = document.getElementById("downloadStage");
const speedEvt = new EventSource("/download/speed");
const stageEvt = new EventSource("/download/stage");
function startProgress() {
progressWrapper.style.display = "block";
progressBar.style.width = "0%";
progressBarText.style.display = "block";
progressBarText.innerHTML = "0%";
speedElement.style.display = "block";
speedElement.innerHTML = "0 Mib/s";
}
function showProgress() {
progressWrapper.style.display = "block";
progressBarText.style.display = "block";
speedElement.style.display = "block";
// Show stop button
const stopBtn = document.getElementById("stopBtn");
if (stopBtn) stopBtn.style.display = "inline-block";
if (stopBtn) stopBtn.style.display = "inline-flex";
}
function updateProgress(percent) {
// Show progress div and stop button only when percent > 0
if (percent > 0 && progressWrapper.style.display !== "block") {
showProgress();
}
progressBar.style.width = `${percent}%`;
progressBarText.innerHTML = `${percent}%`;
}
@@ -33,6 +42,9 @@ function resetProgress() {
speedElement.textContent = "0 Mib/s";
speedElement.style.display = "none";
stageElement.textContent = "";
stageElement.style.display = "none";
// Hide stop button
const stopBtn = document.getElementById("stopBtn");
if (stopBtn) stopBtn.style.display = "none";
@@ -48,8 +60,11 @@ evtSource.onmessage = e => {
}
if (e.data === "done") {
resetProgress();
window.electronAPI.setProgress(-1);
// Keep progress bar visible for better UX, let toast handle the feedback
setTimeout(() => {
resetProgress();
window.electronAPI.setProgress(-1);
}, 2000); // Wait 2s so user sees 100% progress
return;
}
@@ -58,14 +73,16 @@ evtSource.onmessage = e => {
if (!isNaN(percent)) {
updateProgress(percent);
window.electronAPI.setProgress(percent); // Update Task Bar
if (percent >= 100) setTimeout(() => {
resetProgress();
window.electronAPI.setProgress(-1); // Remove the bar
}, 500);
// Don't hide at 100%, wait for "done" signal instead
}
};
speedEvt.onmessage = e => {
speedElement.style.display = "block";
speedElement.textContent = e.data; // ex: "5.2MiB/s"
};
stageEvt.onmessage = e => {
stageElement.style.display = "block";
stageElement.textContent = e.data; // ex: "📥 Downloading..."
};

54
public/script/toast.js Normal file
View File

@@ -0,0 +1,54 @@
window.showToast = function(message, type = 'info', duration = 4000) {
const container = document.getElementById('toast-container');
if (!container) return;
// Create toast element
const toast = document.createElement('div');
toast.classList.add('toast', type);
// Icons for different types
const icons = {
success: '✓',
error: '✕',
warning: '⚠',
info: ''
};
const icon = icons[type] || icons.info;
toast.innerHTML = `
<span class="toast-icon">${icon}</span>
<span class="toast-message">${message}</span>
<button class="toast-close" title="Close">×</button>
`;
// Add close functionality
const closeBtn = toast.querySelector('.toast-close');
closeBtn.addEventListener('click', () => removeToast(toast));
// Add to container
container.appendChild(toast);
// Auto remove after duration
if (duration > 0) {
setTimeout(() => removeToast(toast), duration);
}
return toast;
};
function removeToast(toast) {
if (!toast.parentElement) return;
toast.classList.add('removing');
setTimeout(() => {
if (toast.parentElement) {
toast.remove();
}
}, 300);
}
window.showSuccess = (message, duration = 4000) => window.showToast(message, 'success', duration);
window.showError = (message, duration = 5000) => window.showToast(message, 'error', duration);
window.showWarning = (message, duration = 4000) => window.showToast(message, 'warning', duration);
window.showInfo = (message, duration = 4000) => window.showToast(message, 'info', duration);