From f61ed31bd56d93b314e7798693c57595246b0e9c Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Sun, 21 Apr 2024 15:02:27 +0200 Subject: [PATCH] Globally inform about available webUI updates In case the automatic webUI updates setting was disabled the user was never informed about available webUI updates --- public/locales/en.json | 3 + src/components/util/WebUIUpdateChecker.tsx | 73 ++++++++++++++++++++++ src/util/useUpdateChecker.tsx | 31 +++++++-- 3 files changed, 103 insertions(+), 4 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index d5220546..dde40666 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -310,6 +310,7 @@ "dont_show_dialog_again": "Don't show this dialog again", "edit": "Edit", "filter": "Filter", + "ignore": "Ignore", "latest": "Latest", "log_in": "Log in", "log_out": "Log out", @@ -319,6 +320,7 @@ "options": "Options", "popular": "Popular", "refresh": "Refresh", + "remind_later": "Remind later", "remove": "Remove", "reset": "Reset", "reset_to_default": "Reset to Default", @@ -765,6 +767,7 @@ "label": { "channel": "$t(settings.webui.title.webui) channel", "github": "$t(global.label.github) $t(settings.webui.title.webui)", + "info": "$t(settings.webui.title.webui) version {{version}} ({{channel}}) available for download", "update_failure": "Could not update $t(settings.webui.title.webui)", "update_success": "Updated $t(settings.webui.title.webui) to version {{version}}.", "updated": "Updated version", diff --git a/src/components/util/WebUIUpdateChecker.tsx b/src/components/util/WebUIUpdateChecker.tsx index 7e4cd813..9d5a5c78 100644 --- a/src/components/util/WebUIUpdateChecker.tsx +++ b/src/components/util/WebUIUpdateChecker.tsx @@ -14,12 +14,14 @@ import DialogContent from '@mui/material/DialogContent'; import DialogContentText from '@mui/material/DialogContentText'; import DialogActions from '@mui/material/DialogActions'; import Button from '@mui/material/Button'; +import Stack from '@mui/material/Stack'; import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts'; import { UpdateState, WebUiChannel, WebUiUpdateStatus } from '@/lib/graphql/generated/graphql.ts'; import { useLocalStorage } from '@/util/useStorage.tsx'; import { requestManager } from '@/lib/requests/RequestManager.ts'; import { makeToast } from '@/components/util/Toast.tsx'; import { ABOUT_WEBUI, WEBUI_UPDATE_CHECK } from '@/lib/graphql/Fragments.ts'; +import { useUpdateChecker } from '@/util/useUpdateChecker.tsx'; export const WebUIUpdateChecker = () => { const { t } = useTranslation(); @@ -27,6 +29,14 @@ export const WebUIUpdateChecker = () => { const [webUIVersion, setWebUIVersion] = useLocalStorage('webUIVersion'); const [open, setOpen] = useState(false); + const serverSettings = requestManager.useGetServerSettings(); + const isAutoUpdateEnabled = !serverSettings.data?.settings.webUIUpdateCheckInterval; + + const { data: webUIUpdateData, refetch: checkForUpdate } = requestManager.useCheckForWebUIUpdate({ + skip: isAutoUpdateEnabled, + notifyOnNetworkStatusChange: true, + }); + const { data: webUIUpdateStatusData } = requestManager.useGetWebUIUpdateStatus(); const { state: webUIUpdateState, ...updateStatus } = (webUIUpdateStatusData?.getWebUIUpdateStatus ?? { state: UpdateState.Idle, @@ -34,6 +44,8 @@ export const WebUIUpdateChecker = () => { info: undefined, }) satisfies OptionalProperty; + const updateChecker = useUpdateChecker('webUI', checkForUpdate, webUIUpdateData?.checkForWebUIUpdate.tag); + const changelogUrl = updateStatus.info?.channel === WebUiChannel.Stable ? `https://github.com/Suwayomi/Suwayomi-WebUI/releases/tag/${updateStatus.info?.tag}` @@ -91,6 +103,67 @@ export const WebUIUpdateChecker = () => { setOpen(true); }, [webUIUpdateState]); + const isUpdateAvailable = updateChecker.handleUpdate && webUIUpdateData?.checkForWebUIUpdate.updateAvailable; + if (isUpdateAvailable) { + const isUpdateInProgress = webUIUpdateState === UpdateState.Downloading; + + return ( + + {t('global.update.label.available')} + + + {t('settings.about.webui.label.info', { + version: webUIUpdateData.checkForWebUIUpdate.tag, + channel: webUIUpdateData.checkForWebUIUpdate.channel, + })} + + + + + + + + + + + + + + ); + } + const handleUpdate = open && webUIUpdateState === UpdateState.Idle; if (!handleUpdate) { return null; diff --git a/src/util/useUpdateChecker.tsx b/src/util/useUpdateChecker.tsx index 3d39d642..49fa84bc 100644 --- a/src/util/useUpdateChecker.tsx +++ b/src/util/useUpdateChecker.tsx @@ -6,18 +6,27 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { useEffect } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts'; import { useLocalStorage } from '@/util/useStorage.tsx'; -const UPDATE_CHECK_INTERVAL = 1000 * 60 * 60 * 24; // 1 day +const UPDATE_CHECK_INTERVAL = 1000 * 60 * 60; // 1 hour +const UPDATE_REMINDER_THRESHOLD = 1000 * 60 * 60; // 1 hour export const useUpdateChecker = ( storageKey: string, checkForUpdate: () => Promise, + version?: string, interval: number = UPDATE_CHECK_INTERVAL, -): void => { - const [lastUpdateCheck, setLastUpdateCheck] = useLocalStorage(`UpdateChecker::${storageKey}`, 0); +): { handleUpdate: boolean; ignoreUpdate: () => void; remindLater: () => void } => { + const [lastUpdateCheck, setLastUpdateCheck] = useLocalStorage(`UpdateChecker::${storageKey}::lastUpdateCheck`, 0); + const [ignoreVersionUpdate, setIgnoreVersionUpdate] = useLocalStorage( + `UpdateChecker::${storageKey}::ignoreUpdate`, + ); + const [updateClosedTimestamp, setUpdateClosedTimestamp] = useLocalStorage( + `UpdateChecker::${storageKey}::closeTimestamp`, + 0, + ); useEffect(() => { const remainingTimeTillNextUpdateCheck = (interval - (Date.now() - lastUpdateCheck)) % interval; @@ -35,4 +44,18 @@ export const useUpdateChecker = ( return () => clearTimeout(timeout); }, [storageKey, checkForUpdate, interval]); + + const ignoreUpdate = useCallback(() => { + setIgnoreVersionUpdate(version); + }, [storageKey, version]); + + const remindLater = useCallback(() => { + setUpdateClosedTimestamp(Date.now()); + }, [storageKey]); + + const wasRecentlyClosed = Date.now() - updateClosedTimestamp < UPDATE_REMINDER_THRESHOLD; + const wasUpdateIgnored = !!ignoreVersionUpdate && ignoreVersionUpdate === version; + const handleUpdate = !wasRecentlyClosed && !wasUpdateIgnored; + + return useMemo(() => ({ handleUpdate, ignoreUpdate, remindLater }), [handleUpdate, ignoreUpdate, remindLater]); };