Files
makelore/src/components/layout/SidebarUpdateButton.tsx
inman 80e8386fa6
Some checks failed
Electron E2E / Electron E2E (macos-latest) (push) Has been cancelled
Electron E2E / Electron E2E (ubuntu-latest) (push) Has been cancelled
Electron E2E / Electron E2E (windows-latest) (push) Has been cancelled
feat: update Makelore modules and conversations
2026-07-31 10:08:41 +08:00

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>
);
}