89 lines
3.0 KiB
TypeScript
89 lines
3.0 KiB
TypeScript
import { useEffect } from 'react';
|
|
import { Download, Loader2, RotateCcw } from 'lucide-react';
|
|
import { toast } from 'sonner';
|
|
import { cn } from '@/lib/utils';
|
|
import { useUpdateStore } from '@/stores/update';
|
|
|
|
const ACTIONABLE_STATUSES = new Set(['available', 'downloading', 'downloaded']);
|
|
|
|
function iconState(active: boolean): string {
|
|
return active
|
|
? 'scale-100 opacity-100 blur-0'
|
|
: 'scale-25 opacity-0 blur-[4px]';
|
|
}
|
|
|
|
export function SidebarUpdateButton({ collapsed }: { collapsed: boolean }) {
|
|
const status = useUpdateStore((state) => state.status);
|
|
const version = useUpdateStore((state) => state.updateInfo?.version);
|
|
const init = useUpdateStore((state) => state.init);
|
|
const downloadUpdate = useUpdateStore((state) => state.downloadUpdate);
|
|
const installUpdate = useUpdateStore((state) => state.installUpdate);
|
|
|
|
useEffect(() => {
|
|
void init();
|
|
}, [init]);
|
|
|
|
if (!ACTIONABLE_STATUSES.has(status)) return null;
|
|
|
|
const downloaded = status === 'downloaded';
|
|
const downloading = status === 'downloading';
|
|
const label = downloaded
|
|
? `重启并安装新版本 ${version ?? ''}`.trim()
|
|
: downloading
|
|
? `正在下载新版本 ${version ?? ''}`.trim()
|
|
: `下载新版本 ${version ?? ''}`.trim();
|
|
|
|
const handleClick = async () => {
|
|
if (downloaded) {
|
|
installUpdate();
|
|
return;
|
|
}
|
|
|
|
await downloadUpdate();
|
|
const state = useUpdateStore.getState();
|
|
if (state.status === 'error') {
|
|
toast.error('更新下载失败', {
|
|
description: (state.error ?? '请前往设置查看详情并重试').slice(0, 160),
|
|
});
|
|
}
|
|
};
|
|
|
|
return (
|
|
<button
|
|
type="button"
|
|
data-testid="sidebar-update-button"
|
|
aria-label={label}
|
|
title={label}
|
|
disabled={downloading}
|
|
onClick={() => void handleClick()}
|
|
className={cn(
|
|
'motion-press flex h-10 w-10 shrink-0 items-center justify-center rounded-full border border-accent/30 bg-accent text-primary-foreground shadow-soft hover:bg-accent-strong disabled:cursor-wait disabled:opacity-80',
|
|
collapsed && 'shadow-none',
|
|
)}
|
|
>
|
|
<span className="relative h-4 w-4" aria-hidden="true">
|
|
<Download
|
|
className={cn(
|
|
'absolute inset-0 h-4 w-4 transition-[transform,opacity,filter] duration-300 [transition-timing-function:cubic-bezier(0.2,0,0,1)]',
|
|
iconState(status === 'available'),
|
|
)}
|
|
/>
|
|
<span
|
|
className={cn(
|
|
'absolute inset-0 transition-[transform,opacity,filter] duration-300 [transition-timing-function:cubic-bezier(0.2,0,0,1)]',
|
|
iconState(downloading),
|
|
)}
|
|
>
|
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
</span>
|
|
<RotateCcw
|
|
className={cn(
|
|
'absolute inset-0 h-4 w-4 transition-[transform,opacity,filter] duration-300 [transition-timing-function:cubic-bezier(0.2,0,0,1)]',
|
|
iconState(downloaded),
|
|
)}
|
|
/>
|
|
</span>
|
|
</button>
|
|
);
|
|
}
|