feat: in-app update checker + Spinner import for General settings
- Add useTauriUpdater hook (check_for_update / install_update commands) - Add AppUpdates section to General settings (Tauri-only, hidden on web) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,7 @@ import {
|
|||||||
PopOut,
|
PopOut,
|
||||||
RectCords,
|
RectCords,
|
||||||
Scroll,
|
Scroll,
|
||||||
|
Spinner,
|
||||||
Switch,
|
Switch,
|
||||||
Text,
|
Text,
|
||||||
toRem,
|
toRem,
|
||||||
@@ -63,6 +64,7 @@ import { useMessageLayoutItems } from '../../../hooks/useMessageLayout';
|
|||||||
import { useMessageSpacingItems } from '../../../hooks/useMessageSpacing';
|
import { useMessageSpacingItems } from '../../../hooks/useMessageSpacing';
|
||||||
import { useDateFormatItems } from '../../../hooks/useDateFormat';
|
import { useDateFormatItems } from '../../../hooks/useDateFormat';
|
||||||
import { SequenceCardStyle } from '../styles.css';
|
import { SequenceCardStyle } from '../styles.css';
|
||||||
|
import { useTauriUpdater } from '../../../hooks/useTauriUpdater';
|
||||||
|
|
||||||
type ThemeSelectorProps = {
|
type ThemeSelectorProps = {
|
||||||
themeNames: Record<string, string>;
|
themeNames: Record<string, string>;
|
||||||
@@ -1476,6 +1478,46 @@ function Messages() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function AppUpdates() {
|
||||||
|
const { isTauri, status, check, install } = useTauriUpdater();
|
||||||
|
if (!isTauri) return null;
|
||||||
|
|
||||||
|
const description =
|
||||||
|
status.state === 'checking'
|
||||||
|
? 'Checking for updates...'
|
||||||
|
: status.state === 'up-to-date'
|
||||||
|
? 'Lotus Chat is up to date.'
|
||||||
|
: status.state === 'available'
|
||||||
|
? `Update available: v${status.version}`
|
||||||
|
: status.state === 'installing'
|
||||||
|
? 'Installing update, the app will restart shortly...'
|
||||||
|
: status.state === 'error'
|
||||||
|
? `Update check failed: ${status.message}`
|
||||||
|
: 'Check for a new version of Lotus Chat.';
|
||||||
|
|
||||||
|
const after =
|
||||||
|
status.state === 'available' ? (
|
||||||
|
<Button size="300" radii="300" onClick={install}>
|
||||||
|
<Text size="B300">Install & Restart</Text>
|
||||||
|
</Button>
|
||||||
|
) : status.state === 'checking' || status.state === 'installing' ? (
|
||||||
|
<Spinner variant="Secondary" size="200" />
|
||||||
|
) : (
|
||||||
|
<Button size="300" radii="300" variant="Secondary" onClick={check}>
|
||||||
|
<Text size="B300">Check</Text>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box direction="Column" gap="100">
|
||||||
|
<Text size="L400">App Updates</Text>
|
||||||
|
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||||
|
<SettingTile title="Check for Updates" description={description} after={after} />
|
||||||
|
</SequenceCard>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
type GeneralProps = {
|
type GeneralProps = {
|
||||||
requestClose: () => void;
|
requestClose: () => void;
|
||||||
};
|
};
|
||||||
@@ -1506,6 +1548,7 @@ export function General({ requestClose }: GeneralProps) {
|
|||||||
<Messages />
|
<Messages />
|
||||||
<Privacy />
|
<Privacy />
|
||||||
<Calls />
|
<Calls />
|
||||||
|
<AppUpdates />
|
||||||
</Box>
|
</Box>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
</Scroll>
|
</Scroll>
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { useState, useCallback } from 'react';
|
||||||
|
|
||||||
|
type TauriInternals = { invoke: (cmd: string, args?: Record<string, unknown>) => Promise<unknown> };
|
||||||
|
const tauriInvoke = (): TauriInternals['invoke'] | undefined =>
|
||||||
|
(window as unknown as { __TAURI_INTERNALS__?: TauriInternals }).__TAURI_INTERNALS__?.invoke;
|
||||||
|
|
||||||
|
type UpdateStatus =
|
||||||
|
| { state: 'idle' }
|
||||||
|
| { state: 'checking' }
|
||||||
|
| { state: 'up-to-date' }
|
||||||
|
| { state: 'available'; version: string }
|
||||||
|
| { state: 'installing' }
|
||||||
|
| { state: 'error'; message: string };
|
||||||
|
|
||||||
|
export function useTauriUpdater() {
|
||||||
|
const isTauri = !!tauriInvoke();
|
||||||
|
const [status, setStatus] = useState<UpdateStatus>({ state: 'idle' });
|
||||||
|
|
||||||
|
const check = useCallback(async () => {
|
||||||
|
const invoke = tauriInvoke();
|
||||||
|
if (!invoke) return;
|
||||||
|
setStatus({ state: 'checking' });
|
||||||
|
try {
|
||||||
|
const result = (await invoke('check_for_update')) as { available: boolean; version?: string };
|
||||||
|
setStatus(
|
||||||
|
result.available && result.version
|
||||||
|
? { state: 'available', version: result.version }
|
||||||
|
: { state: 'up-to-date' },
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
setStatus({ state: 'error', message: String(e) });
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const install = useCallback(async () => {
|
||||||
|
const invoke = tauriInvoke();
|
||||||
|
if (!invoke) return;
|
||||||
|
setStatus({ state: 'installing' });
|
||||||
|
try {
|
||||||
|
await invoke('install_update');
|
||||||
|
} catch (e) {
|
||||||
|
setStatus({ state: 'error', message: String(e) });
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { isTauri, status, check, install };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user