import { type ReactNode } from 'react'; import { open } from '@tauri-apps/plugin-shell'; import { ArrowUpCircle, CheckCircle2, ChevronDown, Download, FolderOpen, RefreshCw, ShieldCheck } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { version as currentVersion } from '../../package.json'; import { formatBytes } from '../utils/format/formatBytes'; import { useAppUpdater } from '../hooks/useAppUpdater'; import Modal from './Modal'; import Changelog from './appUpdater/Changelog'; export default function AppUpdater() { const { t } = useTranslation(); const { release, dismissed, setDismissed, changelogOpen, setChangelogOpen, dlState, dlProgress, dlError, countdown, asset, showAurHint, showWingetHint, useTauriUpdater, showInstallBtn, pct, handleSkip, handleRestartNow, handleDownload, handleShowFolder, } = useAppUpdater(); if (!release || dismissed) return null; // Footer actions — state-dependent. Downloading has no actions (no footer). // When there is no in-app install (AUR / from-source), "Remind me later" is the // primary action, so it gets the accent button; Skip stays a clear button. let footer: ReactNode = null; if (dlState === 'idle') { footer = ( <>
{showInstallBtn && ( )} ); } else if (dlState === 'done' && useTauriUpdater) { footer = ( <>
); } else if (dlState === 'done') { footer = ( <>
); } else if (dlState === 'error') { footer = ( <>
); } return ( setDismissed(true)} icon={} title={t('common.updaterModalTitle')} subtitle={<>v{currentVersion} → v{release.version}} closeLabel={t('common.updaterRemindBtn')} footer={footer} > {/* Collapsible changelog */} {release.body && (
{changelogOpen && (
)}
)} {/* Download / AUR area */}
{showAurHint ? (
{t('common.updaterAurHint')}
yay -S psysonic-bin sudo pacman -Syu psysonic-bin
) : useTauriUpdater ? ( <> {dlState === 'idle' && (
{t('common.updaterMacReadyTitle', { defaultValue: 'Ready to install' })}
{t('common.updaterMacReady', { defaultValue: 'The update downloads, verifies and applies in place — no DMG needed. The app restarts automatically when done.', })}
{t('common.updaterTrustNotarized', { defaultValue: 'Notarized by Apple' })} {t('common.updaterTrustSignature', { defaultValue: 'Signature verified' })}
)} {dlState === 'downloading' && (
{pct}% {formatBytes(dlProgress.bytes)} {dlProgress.total > 0 && ` / ${formatBytes(dlProgress.total)}`}
)} {dlState === 'done' && (
{t('common.updaterMacDoneTitle', { defaultValue: 'Update installed' })}
{countdown !== null ? t('common.updaterRestartingIn', { defaultValue: 'Restarting in {{n}}s…', n: countdown }) : t('common.updaterRestarting', { defaultValue: 'Restarting…' })}
)} {dlState === 'error' && (
{dlError || t('common.updaterErrorMsg')}
)} ) : asset ? ( <> {dlState === 'idle' && (
{asset.name} {formatBytes(asset.size)}
)} {dlState === 'idle' && showWingetHint && (
{t('common.updaterWingetHint')}
winget upgrade Psysonic
)} {dlState === 'downloading' && (
{pct}% {formatBytes(dlProgress.bytes)} {dlProgress.total > 0 && ` / ${formatBytes(dlProgress.total)}`}
)} {dlState === 'done' && (
{t('common.updaterDone')}
{t('common.updaterInstallHint')}
)} {dlState === 'error' && (
{dlError || t('common.updaterErrorMsg')}
)} ) : (
)}
); }