Globally inform about available webUI updates

In case the automatic webUI updates setting was disabled the user was never informed about available webUI updates
This commit is contained in:
schroda
2024-04-21 15:02:27 +02:00
parent ad4f59f324
commit f61ed31bd5
3 changed files with 103 additions and 4 deletions

View File

@@ -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",

View File

@@ -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<string>('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<WebUiUpdateStatus, 'info'>;
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 (
<Dialog open>
<DialogTitle>{t('global.update.label.available')}</DialogTitle>
<DialogContent>
<DialogContentText>
{t('settings.about.webui.label.info', {
version: webUIUpdateData.checkForWebUIUpdate.tag,
channel: webUIUpdateData.checkForWebUIUpdate.channel,
})}
</DialogContentText>
</DialogContent>
<DialogActions>
<Stack sx={{ width: '100%' }} direction="row" justifyContent="space-between">
<Button href={changelogUrl} target="_blank">
{t('global.button.changelog')}
</Button>
<Stack direction="row">
<Button
disabled={isUpdateInProgress}
onClick={() => {
updateChecker.remindLater();
setOpen(false);
}}
>
{t('global.button.remind_later')}
</Button>
<Button
disabled={isUpdateInProgress}
onClick={() => {
updateChecker.ignoreUpdate();
setOpen(false);
}}
>
{t('global.button.ignore')}
</Button>
<Button
disabled={isUpdateInProgress}
onClick={() => {
requestManager
.updateWebUI()
.response.catch(() =>
makeToast(t('settings.about.webui.label.update_failure'), 'error'),
);
}}
variant="contained"
>
{isUpdateInProgress
? t('global.update.label.updating', { progress: updateStatus.progress })
: t('extension.action.label.update')}
</Button>
</Stack>
</Stack>
</DialogActions>
</Dialog>
);
}
const handleUpdate = open && webUIUpdateState === UpdateState.Idle;
if (!handleUpdate) {
return null;

View File

@@ -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<unknown>,
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<string>(
`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]);
};