From da10502bdd5ebfd5fe016b4d6ecd6067f8b13b1d Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Wed, 4 Jun 2025 15:35:38 +0700 Subject: [PATCH] fix: remind me later updater (#5191) * fix: remind me later app updater * chore: update state app updater * chore sync state --- web-app/src/containers/dialogs/AppUpdater.tsx | 27 ++- web-app/src/hooks/useAppUpdater.ts | 167 ++++++++++++------ web-app/src/routes/settings/general.tsx | 9 +- 3 files changed, 141 insertions(+), 62 deletions(-) diff --git a/web-app/src/containers/dialogs/AppUpdater.tsx b/web-app/src/containers/dialogs/AppUpdater.tsx index 79ede8aa7..c9393099f 100644 --- a/web-app/src/containers/dialogs/AppUpdater.tsx +++ b/web-app/src/containers/dialogs/AppUpdater.tsx @@ -6,7 +6,7 @@ import { Button } from '@/components/ui/button' import { useState, useEffect } from 'react' import { useReleaseNotes } from '@/hooks/useReleaseNotes' import { RenderMarkdown } from '../RenderMarkdown' -import { isDev } from '@/lib/utils' +import { cn, isDev } from '@/lib/utils' const DialogAppUpdater = () => { const { @@ -38,12 +38,31 @@ const DialogAppUpdater = () => { checkForUpdate() }, [checkForUpdate]) - if (updateState.remindMeLater) return null + const [appUpdateState, setAppUpdateState] = useState({ + remindMeLater: false, + isUpdateAvailable: false, + }) + + useEffect(() => { + setAppUpdateState({ + remindMeLater: updateState.remindMeLater, + isUpdateAvailable: updateState.isUpdateAvailable, + }) + }, [updateState]) + + if (appUpdateState.remindMeLater) return null + + console.log(appUpdateState) + console.log(updateState) return ( <> - {updateState.isUpdateAvailable && ( -
+ {appUpdateState.isUpdateAvailable && ( +
diff --git a/web-app/src/hooks/useAppUpdater.ts b/web-app/src/hooks/useAppUpdater.ts index 7b4245f74..22e860f6b 100644 --- a/web-app/src/hooks/useAppUpdater.ts +++ b/web-app/src/hooks/useAppUpdater.ts @@ -1,6 +1,6 @@ import { isDev } from '@/lib/utils' import { check, Update } from '@tauri-apps/plugin-updater' -import { useState, useCallback } from 'react' +import { useState, useCallback, useEffect } from 'react' import { events, AppEvent } from '@janhq/core' export interface UpdateState { @@ -24,64 +24,125 @@ export const useAppUpdater = () => { remindMeLater: false, }) - const checkForUpdate = useCallback(async (resetRemindMeLater = false) => { - try { - // Reset remindMeLater if requested (e.g., when called from settings) - if (resetRemindMeLater) { - setUpdateState((prev) => ({ - ...prev, - remindMeLater: false, - })) - } - - if (!isDev()) { - // Production mode - use actual Tauri updater - const update = await check() - - if (update) { - setUpdateState((prev) => ({ - ...prev, - isUpdateAvailable: true, - updateInfo: update, - })) - console.log('Update available:', update.version) - return update - } else { - // No update available - reset state - setUpdateState((prev) => ({ - ...prev, - isUpdateAvailable: false, - updateInfo: null, - })) - - return null - } - } else { - setUpdateState((prev) => ({ - ...prev, - isUpdateAvailable: false, - updateInfo: null, - })) - return null - } - } catch (error) { - console.error('Error checking for updates:', error) - // Reset state on error + // Listen for app update state sync events + useEffect(() => { + const handleUpdateStateSync = (newState: Partial) => { setUpdateState((prev) => ({ ...prev, - isUpdateAvailable: false, - updateInfo: null, + ...newState, })) - return null + } + + events.on('onAppUpdateStateSync', handleUpdateStateSync) + + return () => { + events.off('onAppUpdateStateSync', handleUpdateStateSync) } }, []) - const setRemindMeLater = useCallback((remind: boolean) => { - setUpdateState((prev) => ({ - ...prev, - remindMeLater: remind, - })) - }, []) + const syncStateToOtherInstances = useCallback( + (partialState: Partial) => { + // Emit event to sync state across all useAppUpdater instances + events.emit('onAppUpdateStateSync', partialState) + }, + [] + ) + + const checkForUpdate = useCallback( + async (resetRemindMeLater = false) => { + try { + // Reset remindMeLater if requested (e.g., when called from settings) + if (resetRemindMeLater) { + const newState = { + remindMeLater: false, + } + setUpdateState((prev) => ({ + ...prev, + ...newState, + })) + // Sync to other instances + syncStateToOtherInstances(newState) + } + + if (!isDev()) { + // Production mode - use actual Tauri updater + const update = await check() + + if (update) { + const newState = { + isUpdateAvailable: true, + remindMeLater: false, + updateInfo: update, + } + setUpdateState((prev) => ({ + ...prev, + ...newState, + })) + // Sync to other instances + syncStateToOtherInstances(newState) + console.log('Update available:', update.version) + return update + } else { + // No update available - reset state + const newState = { + isUpdateAvailable: false, + updateInfo: null, + } + setUpdateState((prev) => ({ + ...prev, + ...newState, + })) + // Sync to other instances + syncStateToOtherInstances(newState) + return null + } + } else { + const newState = { + isUpdateAvailable: false, + updateInfo: null, + ...(resetRemindMeLater && { remindMeLater: false }), + } + setUpdateState((prev) => ({ + ...prev, + ...newState, + })) + // Sync to other instances + syncStateToOtherInstances(newState) + return null + } + } catch (error) { + console.error('Error checking for updates:', error) + // Reset state on error + const newState = { + isUpdateAvailable: false, + updateInfo: null, + } + setUpdateState((prev) => ({ + ...prev, + ...newState, + })) + // Sync to other instances + syncStateToOtherInstances(newState) + return null + } + }, + [syncStateToOtherInstances] + ) + + const setRemindMeLater = useCallback( + (remind: boolean) => { + const newState = { + remindMeLater: remind, + } + setUpdateState((prev) => ({ + ...prev, + ...newState, + })) + // Sync to other instances + syncStateToOtherInstances(newState) + }, + [syncStateToOtherInstances] + ) const downloadAndInstallUpdate = useCallback(async () => { if (!updateState.updateInfo) return diff --git a/web-app/src/routes/settings/general.tsx b/web-app/src/routes/settings/general.tsx index 62ee09edd..ae7f45576 100644 --- a/web-app/src/routes/settings/general.tsx +++ b/web-app/src/routes/settings/general.tsx @@ -9,7 +9,7 @@ import LanguageSwitcher from '@/containers/LanguageSwitcher' import { useTranslation } from 'react-i18next' import { useGeneralSetting } from '@/hooks/useGeneralSetting' import { useAppUpdater } from '@/hooks/useAppUpdater' -import { useEffect, useState } from 'react' +import { useEffect, useState, useCallback } from 'react' import { open } from '@tauri-apps/plugin-dialog' import { revealItemInDir } from '@tauri-apps/plugin-opener' import ChangeDataFolderLocation from '@/containers/dialogs/ChangeDataFolderLocation' @@ -63,7 +63,7 @@ const openFileTitle = (): string => { function General() { const { t } = useTranslation() const { spellCheckChatInput, setSpellCheckChatInput } = useGeneralSetting() - const { checkForUpdate, setRemindMeLater } = useAppUpdater() + const { checkForUpdate } = useAppUpdater() const [janDataFolder, setJanDataFolder] = useState() const [isCopied, setIsCopied] = useState(false) const [selectedNewPath, setSelectedNewPath] = useState(null) @@ -179,9 +179,8 @@ function General() { } } - const handleCheckForUpdate = async () => { + const handleCheckForUpdate = useCallback(async () => { setIsCheckingUpdate(true) - setRemindMeLater(false) try { if (isDev()) return toast.info('You are running a development version of Jan!') @@ -196,7 +195,7 @@ function General() { } finally { setIsCheckingUpdate(false) } - } + }, [setIsCheckingUpdate, checkForUpdate]) return (