diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index b47ad54c5..ffd0ccd76 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -160,7 +160,8 @@ "clean": "clean", "gridRows": "grid rows", "tableColumns": "table columns", - "itemsMore": "{{count}} more" + "itemsMore": "{{count}} more", + "newVersionAvailable": "a new version is available" }, "entity": { "album_one": "album", diff --git a/src/main/index.ts b/src/main/index.ts index 37589ed69..5e9252fad 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -68,16 +68,40 @@ class AppUpdater { const effectiveChannel = store.get('release_channel') as string; console.log('Effective update channel:', effectiveChannel); if (effectiveChannel === 'alpha') { - checkAllChannelsAndGetBest().then(({ updater: updaterInstance }) => { + checkAllChannelsAndGetBest().then(({ result, updater: updaterInstance }) => { updaterInstance.autoInstallOnAppQuit = true; updaterInstance.autoRunAppAfterInstall = true; - updaterInstance.checkForUpdatesAndNotify(); + if (isMacOS()) { + if (result?.isUpdateAvailable) { + getMainWindow()?.webContents.send( + 'update-available', + result.updateInfo.version, + ); + } + } else { + updaterInstance.checkForUpdatesAndNotify(); + } }); return; } configureAndGetUpdater(); - autoUpdater.checkForUpdatesAndNotify(); + if (isMacOS()) { + autoUpdater.autoDownload = false; + autoUpdater + .checkForUpdates() + .then((result) => { + if (result?.isUpdateAvailable) { + getMainWindow()?.webContents.send( + 'update-available', + result.updateInfo.version, + ); + } + }) + .catch((err) => console.error('Check for updates failed', err)); + } else { + autoUpdater.checkForUpdatesAndNotify(); + } } } @@ -555,8 +579,15 @@ async function createWindow(first = true): Promise { const updateAvailable = result?.isUpdateAvailable ?? false; console.log('Update available:', updateAvailable); if (updateAvailable && store.get('disable_auto_updates') !== true) { - console.log('Downloading update'); - updater.downloadUpdate(); + if (isMacOS()) { + getMainWindow()?.webContents.send( + 'update-available', + result?.updateInfo?.version, + ); + } else { + console.log('Downloading update'); + updater.downloadUpdate(); + } } return { diff --git a/src/preload/ipc.ts b/src/preload/ipc.ts index d4641d1a9..56e2b2c55 100644 --- a/src/preload/ipc.ts +++ b/src/preload/ipc.ts @@ -12,9 +12,19 @@ const invoke = (channel: string, ...args: any[]) => { return ipcRenderer.invoke(channel, ...args); }; +const on = (channel: string, listener: (event: any, ...args: any[]) => void) => { + ipcRenderer.on(channel, listener); +}; + +const removeListener = (channel: string, listener: (event: any, ...args: any[]) => void) => { + ipcRenderer.removeListener(channel, listener); +}; + export const ipc = { invoke, + on, removeAllListeners, + removeListener, send, }; diff --git a/src/renderer/app.tsx b/src/renderer/app.tsx index 3bbcdb58a..275bcbe76 100644 --- a/src/renderer/app.tsx +++ b/src/renderer/app.tsx @@ -29,6 +29,12 @@ const ReleaseNotesModal = lazy(() => })), ); +const UpdateAvailableDialog = lazy(() => + import('./update-available-dialog').then((module) => ({ + default: module.UpdateAvailableDialog, + })), +); + const ipc = isElectron() ? window.api.ipc : null; export const App = () => { @@ -118,6 +124,7 @@ export const App = () => { + ); diff --git a/src/renderer/update-available-dialog.tsx b/src/renderer/update-available-dialog.tsx new file mode 100644 index 000000000..716605d35 --- /dev/null +++ b/src/renderer/update-available-dialog.tsx @@ -0,0 +1,79 @@ +import isElectron from 'is-electron'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { Button } from '/@/shared/components/button/button'; +import { Dialog } from '/@/shared/components/dialog/dialog'; +import { Group } from '/@/shared/components/group/group'; +import { Icon } from '/@/shared/components/icon/icon'; +import { Stack } from '/@/shared/components/stack/stack'; +import { Text } from '/@/shared/components/text/text'; +import { useLocalStorage } from '/@/shared/hooks/use-local-storage'; + +export const UpdateAvailableDialog = () => { + const [opened, setOpened] = useState(false); + const [version, setVersion] = useState(''); + const { t } = useTranslation(); + const [versionDismissed, setVersionDismissed] = useLocalStorage({ + key: 'version_dismissed', + }); + + useEffect(() => { + if (!isElectron()) return; + + const handleUpdateAvailable = (_event: any, newVersion: string) => { + if (versionDismissed !== newVersion) { + setVersion(newVersion); + setOpened(true); + } + }; + + window.api.ipc.on('update-available', handleUpdateAvailable); + + return () => { + window.api.ipc.removeListener?.('update-available', handleUpdateAvailable); + }; + }, [versionDismissed]); + + if (!opened) return null; + + const handleDismiss = () => { + if (version) { + setVersionDismissed(version); + } + setOpened(false); + }; + + return ( + + + + {t('common.newVersionAvailable', { postProcess: 'sentenceCase' })} - {version} + + + + + + + + ); +}; diff --git a/src/shared/components/dialog/dialog.module.css b/src/shared/components/dialog/dialog.module.css index 261cdf1d2..60c755fd7 100644 --- a/src/shared/components/dialog/dialog.module.css +++ b/src/shared/components/dialog/dialog.module.css @@ -3,3 +3,8 @@ background: var(--theme-colors-surface); box-shadow: 2px 2px 10px 2px rgb(0 0 0 / 40%); } + + +.close-button { + display: none; +} diff --git a/src/shared/components/dialog/dialog.tsx b/src/shared/components/dialog/dialog.tsx index f5a9c9bcd..80473a3ca 100644 --- a/src/shared/components/dialog/dialog.tsx +++ b/src/shared/components/dialog/dialog.tsx @@ -9,7 +9,7 @@ interface DialogProps extends MantineDialogProps {} export const Dialog = ({ classNames, style, ...props }: DialogProps) => { return (