OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
The main process already showed a system notification when an update finished downloading, and nothing in the renderer listened — so the only cue vanished on its own and was missed by anyone not watching the desktop at that moment. A prompt now stays in the window until it is acted on or dismissed, with the restart wired to the install-update channel the preload already exposed. Dismissing is 'not now', not 'never': a later version reopens the prompt, since the version it refers to has changed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
54 lines
1.9 KiB
TypeScript
54 lines
1.9 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { isElectron } from '../../platform/platform';
|
|
import { useT } from '../../i18n';
|
|
|
|
/**
|
|
* Aviso de que uma atualização já baixou e só falta reiniciar.
|
|
*
|
|
* O processo principal já mostrava uma notificação do sistema, que some sozinha
|
|
* e passa despercebida se a pessoa não estiver olhando. Este aviso fica na
|
|
* janela até ser atendido ou dispensado.
|
|
*/
|
|
export function UpdateBanner() {
|
|
const t = useT();
|
|
const [version, setVersion] = useState<string | null>(null);
|
|
const [dismissed, setDismissed] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (!isElectron() || !window.backspace?.onUpdateDownloaded) return;
|
|
window.backspace.onUpdateDownloaded((info) => {
|
|
setVersion(info.version);
|
|
// Uma atualização nova reabre o aviso mesmo se a anterior foi dispensada:
|
|
// dispensar significa "agora não", não "nunca mais".
|
|
setDismissed(false);
|
|
});
|
|
}, []);
|
|
|
|
if (!version || dismissed) return null;
|
|
|
|
return (
|
|
<div className="fixed bottom-4 right-4 z-[400] max-w-[320px] glass rounded-xl shadow-xl p-3.5 animate-slide-up">
|
|
<div className="text-[13px] font-semibold text-txt-primary">{t('update.ready')}</div>
|
|
<p className="text-[12px] text-txt-secondary mt-0.5">
|
|
{t('update.readyVersion', { version })}
|
|
</p>
|
|
<div className="flex gap-2 mt-3">
|
|
<button
|
|
type="button"
|
|
onClick={() => window.backspace?.installUpdate?.()}
|
|
className="px-3 py-1.5 rounded-md text-[12px] font-medium bg-accent-primary text-white hover:brightness-110"
|
|
>
|
|
{t('update.restart')}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => setDismissed(true)}
|
|
className="px-3 py-1.5 rounded-md text-[12px] font-medium bg-interactive-muted text-txt-secondary hover:text-txt-primary"
|
|
>
|
|
{t('update.later')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|