From 5211372fc1e7fc6a0525f4d06b0bf238cabdea54 Mon Sep 17 00:00:00 2001 From: Sucukdeluxe <259325684+Sucukdeluxe@users.noreply.github.com> Date: Fri, 21 Aug 2026 00:04:35 +0200 Subject: [PATCH] feat: clarify update progress states Use a full-width green download track, preserve completed progress in red when cancellation or failure occurs, move status copy below the track, and smooth percentage transitions. --- renderer/app.js | 25 ++++++++++++++----------- renderer/styles.css | 15 ++++++++++----- tests/ui-smoke.js | 12 +++++++++--- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/renderer/app.js b/renderer/app.js index a1bffee..0911108 100644 --- a/renderer/app.js +++ b/renderer/app.js @@ -7492,7 +7492,7 @@ function showUpdateBanner(info) { installButton.disabled = false; installButton.textContent = 'Jetzt installieren'; } - _setUpdateProgress(0, 'Bereit zum Download'); + _setUpdateProgress(0, 'Bereit zum Download', 'ready'); _setUpdateDialogBusy(false); _syncHeaderUpdateState(); _setUpdateDialogVisible(true); @@ -7505,38 +7505,39 @@ function handleUpdateProgress(data) { if (progress.stage === 'starting') { _updateInstallBusy = true; _setUpdateDialogBusy(true, true); - _setUpdateProgress(0, 'Download 0%'); + _setUpdateProgress(0, 'Download 0%', 'downloading'); if (message) message.hidden = true; if (button) button.textContent = 'Download 0%'; } else if (progress.stage === 'downloading') { const percent = Math.max(0, Math.min(100, Math.round(Number(progress.percent) || 0))); _updateInstallBusy = true; _setUpdateDialogBusy(true, true); - _setUpdateProgress(percent, `Download ${percent}%`); + _setUpdateProgress(percent, `Download ${percent}%`, 'downloading'); if (message) message.hidden = true; if (button) button.textContent = `Download ${percent}%`; } else if (progress.stage === 'verifying') { _updateInstallBusy = true; _setUpdateDialogBusy(true, false); - _setUpdateProgress(100, 'Prüfen…'); + _setUpdateProgress(100, 'Prüfen…', 'verifying'); if (message) message.hidden = true; if (button) button.textContent = 'Prüfen…'; } else if (progress.stage === 'prepared') { _updateInstallBusy = true; _setUpdateDialogBusy(true, false); - _setUpdateProgress(100, 'Neustart…'); + _setUpdateProgress(100, 'Neustart…', 'prepared'); if (message) message.hidden = true; if (button) button.textContent = 'Neustart…'; } else if (progress.stage === 'launching' || progress.stage === 'done') { _updateInstallBusy = true; _setUpdateDialogBusy(true, false); - _setUpdateProgress(100, 'Neustart…'); + _setUpdateProgress(100, 'Neustart…', 'prepared'); if (message) message.hidden = true; if (button) button.textContent = 'Neustart…'; } else if (progress.stage === 'aborted') { _updateInstallBusy = false; _setUpdateDialogBusy(false); - _setUpdateProgress(0, 'Download abgebrochen'); + const currentProgress = Number(document.getElementById('updateProgressBar')?.getAttribute('aria-valuenow')) || 0; + _setUpdateProgress(currentProgress, 'Download abgebrochen', 'aborted'); if (message) { message.hidden = false; message.textContent = 'Download abgebrochen'; @@ -7549,7 +7550,8 @@ function handleUpdateProgress(data) { } else if (progress.stage === 'error') { _updateInstallBusy = false; _setUpdateDialogBusy(false); - _setUpdateProgress(0, 'Update fehlgeschlagen'); + const currentProgress = Number(document.getElementById('updateProgressBar')?.getAttribute('aria-valuenow')) || 0; + _setUpdateProgress(currentProgress, 'Update fehlgeschlagen', 'error'); if (message) { message.hidden = false; message.textContent = `Update fehlgeschlagen: ${String(progress.error || 'Unbekannter Fehler').slice(0, 400)}`; @@ -7669,7 +7671,7 @@ async function handleUpdateDismiss() { if (!_updateDownloadCancelable || _updateCancelBusy) return false; _updateCancelBusy = true; _setUpdateDialogBusy(true, true); - _setUpdateProgress(Number(document.getElementById('updateProgressBar')?.getAttribute('aria-valuenow')) || 0, 'Abbrechen…'); + _setUpdateProgress(Number(document.getElementById('updateProgressBar')?.getAttribute('aria-valuenow')) || 0, 'Abbrechen…', 'aborted'); try { const canceled = await window.api.abortUpdate(); if (!canceled) handleUpdateProgress({ stage: 'error', error: 'Update konnte nicht abgebrochen werden' }); @@ -7699,7 +7701,7 @@ function _setUpdateDialogBusy(busy, cancelable = false) { if (busy && dialog && (!dialog.contains(document.activeElement) || document.activeElement.matches?.(':disabled'))) dialog.focus(); } -function _setUpdateProgress(percent, text) { +function _setUpdateProgress(percent, text, state = 'ready') { const value = Math.max(0, Math.min(100, Math.round(Number(percent) || 0))); const progressText = document.getElementById('updateProgressText'); const progressBar = document.getElementById('updateProgressBar'); @@ -7707,6 +7709,7 @@ function _setUpdateProgress(percent, text) { if (progressBar) { progressBar.setAttribute('aria-valuenow', String(value)); progressBar.setAttribute('aria-valuetext', String(text || `${value}%`)); + progressBar.dataset.state = state; if ('value' in progressBar) progressBar.value = value; progressBar.style.width = `${value}%`; } @@ -7720,7 +7723,7 @@ async function installKnownUpdate() { } _updateInstallBusy = true; _setUpdateDialogBusy(true, true); - _setUpdateProgress(0, 'Download 0%'); + _setUpdateProgress(0, 'Download 0%', 'downloading'); const message = document.getElementById('updateMessage'); const button = document.getElementById('installUpdateBtn'); if (message) message.hidden = true; diff --git a/renderer/styles.css b/renderer/styles.css index 063e9b2..3fe6992 100644 --- a/renderer/styles.css +++ b/renderer/styles.css @@ -2424,9 +2424,8 @@ select.hs-input { max-width: none; width: auto; min-width: 140px; } .update-progress { grid-column: 1 / -1; display: grid; - grid-template-columns: minmax(0, 1fr) auto; - align-items: center; - gap: 10px; + grid-template-columns: minmax(0, 1fr); + gap: 7px; margin-top: 22px; } @@ -2473,12 +2472,18 @@ select.hs-input { max-width: none; width: auto; min-width: 140px; } height: 100%; display: block; border-radius: inherit; - background: var(--accent); - transition: width .2s ease; + background: var(--success); + transition: width .48s cubic-bezier(.22, 1, .36, 1), background-color .16s ease; +} + +.update-progress-track span[data-state="aborted"], +.update-progress-track span[data-state="error"] { + background: var(--danger); } #updateProgressText { min-width: 34px; + justify-self: end; color: var(--text-dim); font-size: 11px; text-align: right; diff --git a/tests/ui-smoke.js b/tests/ui-smoke.js index 433c443..9ca0a02 100644 --- a/tests/ui-smoke.js +++ b/tests/ui-smoke.js @@ -2475,15 +2475,21 @@ setTimeout(async () => { messageHidden: document.getElementById('updateMessage').hidden, messageText: document.getElementById('updateMessage').textContent, progressLabel: progress.getAttribute('aria-label'), - progressText: progress.getAttribute('aria-valuetext') + progressText: progress.getAttribute('aria-valuetext'), + progressState: progress.dataset.state, + progressColor: getComputedStyle(progress).backgroundColor, + progressTrackWidth: progress.parentElement.getBoundingClientRect().width, + progressTextTop: document.getElementById('updateProgressText').getBoundingClientRect().top, + progressTrackBottom: progress.parentElement.getBoundingClientRect().bottom }; })()\`); check('Busy update keeps its progress dialog open with a dangerous download cancellation action', busyUpdateState.display === 'flex' && busyUpdateState.hidden === 'false' && busyUpdateState.closeDisabled === true && busyUpdateState.dismissDisabled === false && busyUpdateState.dismissText === 'Download abbrechen' && busyUpdateState.dismissDanger === true && busyUpdateState.headerHidden === false); check('Update progress exposes an accessible live value', busyUpdateState.progressLabel === 'Update-Fortschritt' && busyUpdateState.progressText === 'Download 50%'); + check('Update download progress is green, wide, and places its status below the line', busyUpdateState.progressState === 'downloading' && busyUpdateState.progressColor === 'rgb(117, 211, 155)' && busyUpdateState.progressTrackWidth >= 390 && busyUpdateState.progressTextTop >= busyUpdateState.progressTrackBottom); check('Busy update shows progress only below the bar', busyUpdateState.messageHidden === true && busyUpdateState.messageText === 'Update v9.9.9 verfügbar'); - const updateErrorRecovery = await wc.executeJavaScript('handleUpdateProgress({ stage: "error", error: "Netzwerkfehler" }); document.getElementById("dismissUpdateBtn").click(); document.getElementById("updateBanner").style.display + "|" + document.getElementById("updateCloseBtn").disabled + "|" + document.getElementById("dismissUpdateBtn").disabled + "|" + document.getElementById("headerUpdateBtn").hidden'); - check('Update errors restore all close actions', updateErrorRecovery === 'none|false|false|false'); + const updateErrorRecovery = await wc.executeJavaScript('handleUpdateProgress({ stage: "aborted", error: "Update abgebrochen" }); (() => { const bar = document.getElementById("updateProgressBar"); const state = [bar.style.width, bar.dataset.state, getComputedStyle(bar).backgroundColor].join("|"); document.getElementById("dismissUpdateBtn").click(); return state + "|" + document.getElementById("updateBanner").style.display + "|" + document.getElementById("updateCloseBtn").disabled + "|" + document.getElementById("dismissUpdateBtn").disabled + "|" + document.getElementById("headerUpdateBtn").hidden; })()'); + check('Canceled updates preserve progress in red and restore all close actions', updateErrorRecovery === '50%|aborted|rgb(255, 133, 140)|none|false|false|false'); const initialInstallUpdateHandler = initialIpcHandlers.get('app:install-update'); const initialUpdateQueueHandler = initialIpcHandlers.get('save-pending-queue');