feat: enhance update dialog with changelog notes
This commit is contained in:
@@ -5,7 +5,8 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { RiDownloadLine, RiExternalLinkLine, RiLoaderLine, RiRestartLine, RiSparklingLine } from '@remixicon/react';
|
||||
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
|
||||
import { RiDownloadCloudLine, RiDownloadLine, RiExternalLinkLine, RiLoaderLine, RiRestartLine } from '@remixicon/react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { UpdateInfo, UpdateProgress } from '@/lib/desktop';
|
||||
|
||||
@@ -47,24 +48,38 @@ export const UpdateDialog: React.FC<UpdateDialogProps> = ({
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<RiSparklingLine className="h-5 w-5 text-primary" />
|
||||
<RiDownloadCloudLine className="h-5 w-5 text-primary" />
|
||||
Update Available
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 mt-2">
|
||||
{info?.currentVersion && (
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-muted-foreground">Current version</span>
|
||||
<span className="font-mono">{info.currentVersion}</span>
|
||||
{(info?.currentVersion || info?.version) && (
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
{info?.currentVersion && (
|
||||
<span className="font-mono">{info.currentVersion}</span>
|
||||
)}
|
||||
{info?.currentVersion && info?.version && (
|
||||
<span className="text-muted-foreground">→</span>
|
||||
)}
|
||||
{info?.version && (
|
||||
<span className="font-mono text-primary">{info.version}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{info?.version && (
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-muted-foreground">New version</span>
|
||||
<span className="font-mono text-primary">{info.version}</span>
|
||||
</div>
|
||||
{info?.body && (
|
||||
<ScrollableOverlay
|
||||
className="max-h-48 rounded-md border border-border bg-muted/30 p-3"
|
||||
fillContainer={false}
|
||||
>
|
||||
<div className="text-sm text-muted-foreground whitespace-pre-wrap pr-3">
|
||||
{info.body
|
||||
.replace(/^## \[(\d+\.\d+\.\d+)\] - \d{4}-\d{2}-\d{2}\s*/gm, '— v$1 —\n')
|
||||
.replace(/^- /gm, '• ')
|
||||
.trim()}
|
||||
</div>
|
||||
</ScrollableOverlay>
|
||||
)}
|
||||
|
||||
{downloading && (
|
||||
@@ -94,15 +109,14 @@ export const UpdateDialog: React.FC<UpdateDialogProps> = ({
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={cn(
|
||||
'flex-1 flex items-center justify-center gap-2 px-3 py-1.5 rounded-md',
|
||||
'text-sm font-medium',
|
||||
'border border-border',
|
||||
'hover:bg-accent hover:text-accent-foreground',
|
||||
'flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-md',
|
||||
'text-sm text-muted-foreground',
|
||||
'hover:text-foreground hover:bg-accent',
|
||||
'transition-colors'
|
||||
)}
|
||||
>
|
||||
<RiExternalLinkLine className="h-4 w-4" />
|
||||
View Release Notes
|
||||
GitHub
|
||||
</a>
|
||||
|
||||
{!downloaded && !downloading && (
|
||||
|
||||
@@ -25,31 +25,43 @@ export type UseUpdateCheckReturn = UpdateState & {
|
||||
dismiss: () => void;
|
||||
};
|
||||
|
||||
const MOCK_UPDATE: UpdateState = {
|
||||
interface MockUpdateConfig {
|
||||
currentVersion?: string;
|
||||
newVersion?: string;
|
||||
}
|
||||
|
||||
// Set window.__OPENCHAMBER_MOCK_UPDATE__ = { currentVersion: '1.0.3', newVersion: '1.0.8' } to test with real changelog
|
||||
declare global {
|
||||
interface Window {
|
||||
__OPENCHAMBER_MOCK_UPDATE__?: boolean | MockUpdateConfig;
|
||||
}
|
||||
}
|
||||
|
||||
const getMockConfig = (): MockUpdateConfig | null => {
|
||||
if (typeof window === 'undefined') return null;
|
||||
const mock = window.__OPENCHAMBER_MOCK_UPDATE__;
|
||||
if (!mock) return null;
|
||||
if (mock === true) return { currentVersion: '1.0.3', newVersion: '1.0.8' };
|
||||
return mock;
|
||||
};
|
||||
|
||||
const createMockUpdate = (config: MockUpdateConfig): UpdateState => ({
|
||||
checking: false,
|
||||
available: true,
|
||||
downloading: false,
|
||||
downloaded: false,
|
||||
info: {
|
||||
available: true,
|
||||
version: '99.0.0-test',
|
||||
currentVersion: '0.0.0',
|
||||
body: 'Test update for UI development',
|
||||
version: config.newVersion ?? '99.0.0-test',
|
||||
currentVersion: config.currentVersion ?? '0.0.0',
|
||||
body: undefined,
|
||||
},
|
||||
progress: null,
|
||||
error: null,
|
||||
};
|
||||
|
||||
// Set window.__OPENCHAMBER_MOCK_UPDATE__ = true in console to test UI
|
||||
declare global {
|
||||
interface Window {
|
||||
__OPENCHAMBER_MOCK_UPDATE__?: boolean;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const shouldMockUpdate = (): boolean => {
|
||||
if (typeof window === 'undefined') return false;
|
||||
return window.__OPENCHAMBER_MOCK_UPDATE__ === true;
|
||||
return getMockConfig() !== null;
|
||||
};
|
||||
|
||||
export const useUpdateCheck = (checkOnMount = true): UseUpdateCheckReturn => {
|
||||
@@ -64,7 +76,7 @@ export const useUpdateCheck = (checkOnMount = true): UseUpdateCheckReturn => {
|
||||
});
|
||||
|
||||
const [mockMode, setMockMode] = useState(shouldMockUpdate);
|
||||
const [mockState, setMockState] = useState<UpdateState>(MOCK_UPDATE);
|
||||
const [mockState, setMockState] = useState<UpdateState | null>(null);
|
||||
|
||||
// Check for mock mode changes (for console toggling)
|
||||
useEffect(() => {
|
||||
@@ -73,7 +85,10 @@ export const useUpdateCheck = (checkOnMount = true): UseUpdateCheckReturn => {
|
||||
if (shouldMock !== mockMode) {
|
||||
setMockMode(shouldMock);
|
||||
if (shouldMock) {
|
||||
setMockState(MOCK_UPDATE);
|
||||
const config = getMockConfig();
|
||||
if (config) {
|
||||
setMockState(createMockUpdate(config));
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
@@ -82,7 +97,15 @@ export const useUpdateCheck = (checkOnMount = true): UseUpdateCheckReturn => {
|
||||
|
||||
const checkForUpdates = useCallback(async () => {
|
||||
if (mockMode) {
|
||||
setMockState(MOCK_UPDATE);
|
||||
const config = getMockConfig();
|
||||
if (config) {
|
||||
const mockUpdate = createMockUpdate(config);
|
||||
mockUpdate.info = {
|
||||
...mockUpdate.info!,
|
||||
body: `## [${config.newVersion}] - 2025-01-01\n- Mock update for UI testing`,
|
||||
};
|
||||
setMockState(mockUpdate);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!isDesktopRuntime()) {
|
||||
@@ -173,7 +196,7 @@ export const useUpdateCheck = (checkOnMount = true): UseUpdateCheckReturn => {
|
||||
|
||||
const dismiss = useCallback(() => {
|
||||
if (mockMode) {
|
||||
setMockState(MOCK_UPDATE);
|
||||
setMockState(null);
|
||||
return;
|
||||
}
|
||||
setState((prev) => ({ ...prev, available: false, downloaded: false, info: null }));
|
||||
@@ -189,7 +212,7 @@ export const useUpdateCheck = (checkOnMount = true): UseUpdateCheckReturn => {
|
||||
}
|
||||
}, [checkOnMount, checkForUpdates, mockMode]);
|
||||
|
||||
const currentState = mockMode ? mockState : state;
|
||||
const currentState = (mockMode && mockState) ? mockState : state;
|
||||
|
||||
return {
|
||||
...currentState,
|
||||
|
||||
@@ -274,3 +274,4 @@ export const restartToApplyUpdate = async (): Promise<boolean> => {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user