feat(desktop): in-app prompt to restart for an update
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
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>
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
import { UpdateBanner } from '../ui/UpdateBanner';
|
||||||
import { useExpressionStore } from '../../stores/expressionStore';
|
import { useExpressionStore } from '../../stores/expressionStore';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { SpaceSidebar } from './SpaceSidebar';
|
import { SpaceSidebar } from './SpaceSidebar';
|
||||||
@@ -428,6 +429,7 @@ export function AppLayout() {
|
|||||||
(e.g. immediately after Join, or after popping voice-full back to
|
(e.g. immediately after Join, or after popping voice-full back to
|
||||||
the root). */}
|
the root). */}
|
||||||
<SoundController />
|
<SoundController />
|
||||||
|
<UpdateBanner />
|
||||||
<GlobalAudioRenderer />
|
<GlobalAudioRenderer />
|
||||||
<NotificationController />
|
<NotificationController />
|
||||||
<UpdateToast />
|
<UpdateToast />
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -56,6 +56,12 @@ export const en = {
|
|||||||
'accountMenu.copyId': 'Copy User ID',
|
'accountMenu.copyId': 'Copy User ID',
|
||||||
'accountMenu.copied': 'Copied',
|
'accountMenu.copied': 'Copied',
|
||||||
|
|
||||||
|
// Desktop update
|
||||||
|
'update.ready': 'Update ready',
|
||||||
|
'update.readyVersion': 'Version {version} is ready to install.',
|
||||||
|
'update.restart': 'Restart to update',
|
||||||
|
'update.later': 'Later',
|
||||||
|
|
||||||
// Custom emojis and stickers
|
// Custom emojis and stickers
|
||||||
'expressions.title': 'Emojis & Stickers',
|
'expressions.title': 'Emojis & Stickers',
|
||||||
'expressions.emojis': 'Emojis',
|
'expressions.emojis': 'Emojis',
|
||||||
|
|||||||
@@ -55,6 +55,12 @@ export const ptBR: Partial<Dictionary> = {
|
|||||||
'accountMenu.copyId': 'Copiar ID do usuário',
|
'accountMenu.copyId': 'Copiar ID do usuário',
|
||||||
'accountMenu.copied': 'Copiado',
|
'accountMenu.copied': 'Copiado',
|
||||||
|
|
||||||
|
// Atualização do app
|
||||||
|
'update.ready': 'Atualização pronta',
|
||||||
|
'update.readyVersion': 'A versão {version} está pronta para instalar.',
|
||||||
|
'update.restart': 'Reiniciar para atualizar',
|
||||||
|
'update.later': 'Depois',
|
||||||
|
|
||||||
// Emojis e figurinhas
|
// Emojis e figurinhas
|
||||||
'expressions.title': 'Emojis e figurinhas',
|
'expressions.title': 'Emojis e figurinhas',
|
||||||
'expressions.emojis': 'Emojis',
|
'expressions.emojis': 'Emojis',
|
||||||
|
|||||||
Reference in New Issue
Block a user