Skip to content

Commit 656c09a

Browse files
authored
Merge pull request #212 from beNative/codex/enhance-auto-update-functionality-with-notifications
Enhance update notification controls for auto-install
2 parents 41c6809 + 1c5c387 commit 656c09a

File tree

8 files changed

+147
-0
lines changed

8 files changed

+147
-0
lines changed

App.tsx

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,6 +1192,54 @@ export const MainApp: React.FC = () => {
11921192
}
11931193
}, []);
11941194

1195+
useEffect(() => {
1196+
if (typeof window === 'undefined') {
1197+
return;
1198+
}
1199+
if (window.electronAPI) {
1200+
return;
1201+
}
1202+
const params = new URLSearchParams(window.location.search);
1203+
const demoToast = params.get('demoUpdateToast');
1204+
if (!demoToast) {
1205+
return;
1206+
}
1207+
1208+
if (demoToast === 'downloading') {
1209+
const demoProgress = Number(params.get('demoProgress') ?? '68');
1210+
setUpdateToast(prev => ({
1211+
...prev,
1212+
status: 'downloading',
1213+
version: prev.version ?? '0.7.0',
1214+
releaseName: prev.releaseName ?? 'Aurora',
1215+
progress: Number.isFinite(demoProgress) ? Math.max(0, Math.min(100, demoProgress)) : 68,
1216+
bytesTransferred: 45 * 1024 * 1024,
1217+
bytesTotal: 80 * 1024 * 1024,
1218+
visible: true,
1219+
snoozed: false,
1220+
errorMessage: null,
1221+
errorDetails: null,
1222+
}));
1223+
return;
1224+
}
1225+
1226+
if (demoToast === 'downloaded') {
1227+
setUpdateToast(prev => ({
1228+
...prev,
1229+
status: 'downloaded',
1230+
version: prev.version ?? '0.7.0',
1231+
releaseName: prev.releaseName ?? 'Aurora',
1232+
progress: 100,
1233+
bytesTransferred: prev.bytesTotal ?? 80 * 1024 * 1024,
1234+
bytesTotal: prev.bytesTotal ?? 80 * 1024 * 1024,
1235+
visible: true,
1236+
snoozed: false,
1237+
errorMessage: null,
1238+
errorDetails: null,
1239+
}));
1240+
}
1241+
}, []);
1242+
11951243
useEffect(() => {
11961244
if (!window.electronAPI) {
11971245
return;
@@ -2251,6 +2299,12 @@ export const MainApp: React.FC = () => {
22512299
});
22522300
}, []);
22532301

2302+
const handleAutoInstallPreferenceChange = useCallback((enabled: boolean) => {
2303+
addLog('INFO', `User action: ${enabled ? 'Enabled' : 'Disabled'} automatic installation via toast.`);
2304+
const nextSettings = { ...settings, autoInstallUpdates: enabled };
2305+
void saveSettings(nextSettings);
2306+
}, [addLog, saveSettings, settings]);
2307+
22542308
const handleFormatDocument = useCallback(() => {
22552309
const activeDoc = items.find(p => p.id === activeNodeId);
22562310
if (activeDoc && activeDoc.type === 'document' && view === 'editor') {
@@ -3031,6 +3085,9 @@ export const MainApp: React.FC = () => {
30313085
onInstall={updateToast.status === 'downloaded' && window.electronAPI?.quitAndInstallUpdate
30323086
? () => window.electronAPI!.quitAndInstallUpdate!()
30333087
: undefined}
3088+
autoInstallEnabled={settings.autoInstallUpdates}
3089+
autoInstallSupported={isElectron && !!window.electronAPI?.quitAndInstallUpdate}
3090+
onAutoInstallChange={handleAutoInstallPreferenceChange}
30343091
onClose={handleUpdateToastClose}
30353092
/>
30363093
)}

components/SettingsView.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1929,6 +1929,17 @@ const GeneralSettingsSection: React.FC<Pick<SectionProps, 'settings' | 'setCurre
19291929
<SettingRow htmlFor="autoCheckForUpdates" label="Automatic Update Checks" description="Check for new releases whenever DocForge starts.">
19301930
<ToggleSwitch id="autoCheckForUpdates" checked={settings.autoCheckForUpdates} onChange={(val) => setCurrentSettings(s => ({...s, autoCheckForUpdates: val}))} />
19311931
</SettingRow>
1932+
<SettingRow
1933+
htmlFor="autoInstallUpdates"
1934+
label="Automatic Installation"
1935+
description="When enabled, DocForge installs downloaded updates automatically the next time you restart."
1936+
>
1937+
<ToggleSwitch
1938+
id="autoInstallUpdates"
1939+
checked={settings.autoInstallUpdates}
1940+
onChange={(val) => setCurrentSettings(s => ({ ...s, autoInstallUpdates: val }))}
1941+
/>
1942+
</SettingRow>
19321943
<SettingRow label="Check for Updates" description="Run an update check immediately.">
19331944
<div className="flex flex-col items-start md:items-end gap-2 w-full">
19341945
<Button variant="secondary" onClick={handleManualUpdateCheck} isLoading={isManualCheckRunning} disabled={isManualCheckRunning || !canManuallyCheckForUpdates}>

components/UpdateNotification.tsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import ReactDOM from 'react-dom';
33
import Button from './Button';
44
import { DownloadIcon, XIcon, CheckIcon, InfoIcon } from './Icons';
55
import IconButton from './IconButton';
6+
import ToggleSwitch from './ToggleSwitch';
67
import { useLogger } from '../hooks/useLogger';
78

89
type UpdateNotificationStatus = 'downloading' | 'downloaded' | 'error';
@@ -16,6 +17,9 @@ interface UpdateNotificationProps {
1617
errorMessage?: string | null;
1718
errorDetails?: string | null;
1819
onInstall?: () => void;
20+
autoInstallEnabled?: boolean;
21+
autoInstallSupported?: boolean;
22+
onAutoInstallChange?: (enabled: boolean) => void;
1923
onClose: () => void;
2024
}
2125

@@ -46,6 +50,9 @@ const UpdateNotification: React.FC<UpdateNotificationProps> = ({
4650
errorMessage,
4751
errorDetails,
4852
onInstall,
53+
autoInstallEnabled,
54+
autoInstallSupported = true,
55+
onAutoInstallChange,
4956
onClose,
5057
}) => {
5158
const { addLog } = useLogger();
@@ -95,6 +102,14 @@ const UpdateNotification: React.FC<UpdateNotificationProps> = ({
95102
}
96103
};
97104

105+
const handleAutoInstallToggle = (value: boolean) => {
106+
if (!onAutoInstallChange) {
107+
return;
108+
}
109+
addLog('INFO', `User action: ${value ? 'Enabled' : 'Disabled'} automatic update installation from toast.`);
110+
onAutoInstallChange(value);
111+
};
112+
98113
const renderContent = () => {
99114
if (status === 'downloaded') {
100115
return (
@@ -103,6 +118,36 @@ const UpdateNotification: React.FC<UpdateNotificationProps> = ({
103118
<p className="text-sm text-text-secondary mt-1">
104119
DocForge version <span className="font-semibold text-text-main">{versionLabel}</span> has been downloaded and is ready to go.
105120
</p>
121+
{typeof autoInstallEnabled === 'boolean' && (
122+
<div className="mt-4 space-y-3 rounded-lg border border-border-color/70 bg-background/40 p-4">
123+
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
124+
<div className="space-y-1">
125+
<p className="text-sm font-semibold text-text-main">Install updates automatically</p>
126+
<p className="text-xs text-text-secondary">
127+
{autoInstallSupported
128+
? 'DocForge will apply the update the next time you quit if this switch stays on.'
129+
: 'Automatic installation is only available in the desktop app.'}
130+
</p>
131+
</div>
132+
<ToggleSwitch
133+
id="update-auto-install"
134+
checked={autoInstallSupported ? autoInstallEnabled : false}
135+
disabled={!autoInstallSupported}
136+
onChange={handleAutoInstallToggle}
137+
/>
138+
</div>
139+
{autoInstallSupported && !autoInstallEnabled && (
140+
<p className="text-xs text-warning">
141+
Auto-install is off. You&apos;ll need to restart DocForge manually to finish this update.
142+
</p>
143+
)}
144+
{!autoInstallSupported && (
145+
<p className="text-xs text-text-secondary">
146+
Switch to the desktop application to control automatic installation.
147+
</p>
148+
)}
149+
</div>
150+
)}
106151
<div className="mt-4 flex gap-3">
107152
{onInstall && (
108153
<Button onClick={handleInstall} variant="primary" className="flex-1">

constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export const DEFAULT_SETTINGS: Settings = {
2626
autoSaveLogs: false,
2727
allowPrerelease: false,
2828
autoCheckForUpdates: true,
29+
autoInstallUpdates: true,
2930
plantumlRendererMode: 'remote',
3031
uiScale: 100,
3132
documentTreeIndent: 16,

electron/main.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ if (process.platform === 'win32') {
5959
let mainWindow: BrowserWindow | null;
6060
let autoCheckEnabled = true;
6161
let pendingAutoUpdateCheck: NodeJS.Timeout | null = null;
62+
let autoInstallOnQuit = true;
63+
64+
const applyAutoInstallPreference = (enabled: boolean) => {
65+
autoInstallOnQuit = enabled;
66+
autoUpdater.autoInstallOnAppQuit = enabled;
67+
console.log(`Automatic update installation on quit ${enabled ? 'enabled' : 'disabled'}.`);
68+
};
69+
70+
applyAutoInstallPreference(autoInstallOnQuit);
6271

6372
const cancelScheduledAutoUpdateCheck = () => {
6473
if (pendingAutoUpdateCheck) {
@@ -385,6 +394,17 @@ app.on('ready', () => {
385394
console.error('Failed to read auto-update preference from settings:', error);
386395
}
387396

397+
try {
398+
const storedAutoInstall = databaseService.getSetting('autoInstallUpdates');
399+
if (typeof storedAutoInstall === 'boolean') {
400+
applyAutoInstallPreference(storedAutoInstall);
401+
} else if (typeof storedAutoInstall !== 'undefined') {
402+
applyAutoInstallPreference(Boolean(storedAutoInstall));
403+
}
404+
} catch (error) {
405+
console.error('Failed to read auto-install preference from settings:', error);
406+
}
407+
388408
if (autoCheckEnabled) {
389409
scheduleAutoUpdateCheck();
390410
} else {
@@ -648,6 +668,9 @@ ipcMain.on('updater:set-auto-check-enabled', (_, enabled: boolean) => {
648668
cancelScheduledAutoUpdateCheck();
649669
}
650670
});
671+
ipcMain.on('updater:set-auto-install-enabled', (_, enabled: boolean) => {
672+
applyAutoInstallPreference(enabled);
673+
});
651674
ipcMain.on('updater:quit-and-install', () => {
652675
autoUpdater.quitAndInstall();
653676
});

electron/preload.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
6363
renderPlantUML: (diagram: string, format: 'svg' = 'svg') => ipcRenderer.invoke('plantuml:render-svg', diagram, format),
6464
updaterSetAllowPrerelease: (allow: boolean) => ipcRenderer.send('updater:set-allow-prerelease', allow),
6565
updaterSetAutoCheckEnabled: (enabled: boolean) => ipcRenderer.send('updater:set-auto-check-enabled', enabled),
66+
updaterSetAutoInstallEnabled: (enabled: boolean) => ipcRenderer.send('updater:set-auto-install-enabled', enabled),
6667
updaterCheckForUpdates: () => ipcRenderer.invoke('updater:check-now') as Promise<ManualUpdateCheckResult>,
6768
onUpdateAvailable: (callback: (info: UpdateAvailableInfo) => void) => {
6869
const handler = (_: IpcRendererEvent, info: UpdateAvailableInfo) => callback(info);

hooks/useSettings.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,13 @@ export const useSettings = () => {
8888
}
8989
}, [settings.autoCheckForUpdates, loaded, addLog]);
9090

91+
useEffect(() => {
92+
if (loaded && isElectron && window.electronAPI?.updaterSetAutoInstallEnabled) {
93+
addLog('DEBUG', `Notifying main process: autoInstallUpdates is ${settings.autoInstallUpdates}`);
94+
window.electronAPI.updaterSetAutoInstallEnabled(settings.autoInstallUpdates);
95+
}
96+
}, [settings.autoInstallUpdates, loaded, addLog]);
97+
9198
const saveSettings = useCallback(async (newSettings: Settings) => {
9299
setSettings(newSettings);
93100
await repository.saveAllSettings(newSettings);

types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ declare global {
4242
) => Promise<{ success: boolean; svg?: string; error?: string; details?: string }>;
4343
updaterSetAllowPrerelease: (allow: boolean) => void;
4444
updaterSetAutoCheckEnabled?: (enabled: boolean) => void;
45+
updaterSetAutoInstallEnabled?: (enabled: boolean) => void;
4546
updaterCheckForUpdates?: () => Promise<ManualUpdateCheckResult>;
4647
onUpdateAvailable?: (callback: (info: UpdateAvailableInfo) => void) => () => void;
4748
onUpdateDownloadProgress?: (callback: (progress: UpdateDownloadProgress) => void) => () => void;
@@ -485,6 +486,7 @@ export interface Settings {
485486
autoSaveLogs: boolean;
486487
allowPrerelease: boolean;
487488
autoCheckForUpdates: boolean;
489+
autoInstallUpdates: boolean;
488490
plantumlRendererMode: 'remote' | 'offline';
489491
uiScale: number;
490492
documentTreeIndent: number;

0 commit comments

Comments
 (0)