import { useState, type ReactElement } from 'react'; import cls from 'classnames'; import log from 'electron-log/renderer'; import { type UpdaterStatus, type ModsStatus } from '~main/types'; import { formatFileSize } from '~common/utils'; import { api } from '~renderer/utils/api'; import { useT } from '~renderer/i18n'; import Button from './styled/Button'; import DialogButton from './styled/DialogButton'; import ClientDirDialog from './ClientDirDialog'; const formatDuration = (seconds: number) => { const s = Math.max(0, Math.round(seconds)); if (s < 60) return `${s}s`; const m = Math.floor(s / 60); const rem = s % 60; if (m < 60) return rem ? `${m}m ${rem}s` : `${m}m`; const h = Math.floor(m / 60); const minRem = m % 60; return minRem ? `${h}h ${minRem}m` : `${h}h`; }; const formatPercent = (progress: number) => `${(progress * 100).toFixed(1)}%`; const ProgressDetails = ({ status }: { status: UpdaterStatus }) => { const t = useT(); const { bytesDone, bytesTotal, bytesPerSecond, etaSeconds, progress } = status; if (bytesTotal === undefined || bytesDone === undefined) return null; const pct = progress !== undefined && progress >= 0 ? formatPercent(progress) : '—'; return (

{pct} {' '} · {formatFileSize(bytesDone)} / {formatFileSize(bytesTotal)} {bytesPerSecond !== undefined && bytesPerSecond > 0 && ( · {formatFileSize(bytesPerSecond)}/s )} {' · '} {etaSeconds !== undefined ? `~${formatDuration(etaSeconds)} ${t('launch.remaining')}` : t('launch.calculating')}

); }; const LaunchPanel = () => { const t = useT(); const [status, setStatus] = useState({ state: 'verifying' }); api.updater.observe.useSubscription(undefined, { onData: setStatus, onError: err => log.error('Updater subscription error:', err) }); const { data: pref } = api.preferences.get.useQuery(); const [modsStatus, setModsStatus] = useState(); api.mods.observe.useSubscription(undefined, { onData: setModsStatus }); const verify = api.updater.verify.useMutation(); const update = api.updater.update.useMutation(); const start = api.launcher.start.useMutation(); const applyMods = api.mods.applyAll.useMutation(); const props: Record< UpdaterStatus['state'], { button: ReactElement; helperText?: ReactElement } > = { verifying: { button: }, serverUnreachable: { button: pref?.version ? ( ) : ( ), helperText: (

{t('launch.errorLabel')}{' '} {t('launch.serverFail')}

{pref?.version ? t('launch.localVersion', { version: pref.version }) : t('launch.tryLater')}

) }, noClient: { button: ( } > {open => ( )} ) }, updateAvailable: { button: ( ), helperText: (

{t('launch.updateAvailable')}

{status.progress !== undefined && status.bytesDone !== undefined && status.bytesTotal !== undefined && ( <> {formatPercent(status.progress)} {' '} · {formatFileSize(status.bytesDone)} /{' '} {formatFileSize(status.bytesTotal)} {t('launch.onDisk')} ·{' '} )} {status.message}{' '} {t('launch.remaining')}

) }, updating: { button: , helperText: (
{status.message && (

{status.message}

)}
) }, upToDate: { button: modsStatus?.dirty ? ( ) : ( ), helperText: (
{modsStatus?.dirty ? (

{t('launch.modsChanged')}

) : (

{t('launch.upToDate')}

)}

{t('launch.version', { version: pref?.version ?? '' })}

) }, failed: { button: ( ), helperText: (

{t('launch.errorLabel')}{' '} {status.message}

{t('launch.verifyHint')}

) } }; return (
{props[status.state].helperText ?? (status.message && (

{status.message}

))} {start.data && !start.data.ok && start.data.error && (

{start.data.error}

)}
{status.progress !== undefined && (
)}
{props[status.state].button}
); }; export default LaunchPanel;