/* * Copyright (C) Contributors to the Suwayomi project * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { useContext, useEffect } from 'react'; import List from '@mui/material/List'; import ListItem from '@mui/material/ListItem'; import ListItemText from '@mui/material/ListItemText'; import { useTranslation } from 'react-i18next'; import ListSubheader from '@mui/material/ListSubheader'; import { Button, CircularProgress, Divider, Stack, Typography } from '@mui/material'; import RefreshIcon from '@mui/icons-material/Refresh'; import DownloadIcon from '@mui/icons-material/Download'; import OpenInNewIcon from '@mui/icons-material/OpenInNew'; import { t as translate } from 'i18next'; import DownloadingIcon from '@mui/icons-material/Downloading'; import { requestManager } from '@/lib/requests/RequestManager.ts'; import { ListItemLink } from '@/components/util/ListItemLink'; import { NavBarContext, useSetDefaultBackTo } from '@/components/context/NavbarContext'; import { LoadingPlaceholder } from '@/components/util/LoadingPlaceholder'; import { GetAboutQuery, UpdateState } from '@/lib/graphql/generated/graphql.ts'; import { ABOUT_WEBUI, WEBUI_UPDATE_CHECK } from '@/lib/graphql/Fragments.ts'; import { makeToast } from '@/components/util/Toast.tsx'; type AboutServer = GetAboutQuery['aboutServer']; export const getVersion = (aboutServer: AboutServer) => { if (aboutServer.buildType === 'Stable') return `${aboutServer.version}`; return `${aboutServer.version}-${aboutServer.revision}`; }; const getBuildTime = (aboutServer: AboutServer) => new Date(aboutServer.buildTime * 1000).toUTCString(); const getUpdateCheckButtonIcon = ( isLoading: boolean, isUpdateAvailable: boolean, updateState?: UpdateState, asLink: boolean = false, ) => { const isUpdateInProgress = updateState === UpdateState.Downloading; if (isUpdateInProgress) { return ; } if (isLoading) { return ; } const isRefreshRequired = !isUpdateAvailable || updateState === UpdateState.Error; if (isRefreshRequired) { return ; } return asLink ? : ; }; const getUpdateCheckButtonText = ( isLoading: boolean, isUpdateAvailable: boolean, error: any, updateState?: UpdateState, progress: number = 0, ) => { const isUpdating = updateState === UpdateState.Downloading; if (isUpdating) { return translate('global.update.label.updating', { progress }); } const didUpdateFail = updateState === UpdateState.Error; if (didUpdateFail) { return translate('global.update.label.update_failure'); } if (isLoading) { return translate('global.update.label.checking'); } if (error) { return translate('global.update.label.check_failure'); } if (isUpdateAvailable) { return translate('global.update.label.available'); } return translate('global.update.label.up_to_date'); }; type BaseVersionInfoProps = { version: string; isCheckingForUpdate: boolean; isUpdateAvailable: boolean; updateCheckError: any; checkForUpdate: () => void; }; type LinkVersionInfoProps = { downloadAsLink: true; url: string; }; type TriggerVersionInfoProps = { triggerUpdate: () => void; updateState: UpdateState; progress: number; }; type VersionInfoProps = | (BaseVersionInfoProps & PropertiesNever & LinkVersionInfoProps) | (BaseVersionInfoProps & TriggerVersionInfoProps & PropertiesNever); const VersionInfo = ({ version, isCheckingForUpdate, isUpdateAvailable, updateCheckError, checkForUpdate, triggerUpdate, updateState, progress, downloadAsLink, url, }: VersionInfoProps) => { const isUpdateInProgress = updateState === UpdateState.Downloading; const onClick = () => { if (isUpdateInProgress) { return; } const shouldCheckForUpdate = !isUpdateAvailable || updateCheckError || updateState === UpdateState.Error; if (shouldCheckForUpdate) { checkForUpdate(); return; } if (isUpdateAvailable) { triggerUpdate?.(); } }; return ( {version} ); }; export function About() { const { t } = useTranslation(); const { setTitle, setAction } = useContext(NavBarContext); useEffect(() => { setTitle(t('settings.about.title')); setAction(null); }, [t]); useSetDefaultBackTo('settings'); const { data } = requestManager.useGetAbout(); const { aboutServer, aboutWebUI } = data ?? {}; const { data: serverUpdateCheckData, loading: isCheckingForServerUpdate, refetch: checkForServerUpdate, error: serverUpdateCheckError, } = requestManager.useCheckForServerUpdate({ notifyOnNetworkStatusChange: true }); const { data: webUIUpdateData, loading: isCheckingForWebUIUpdate, refetch: checkForWebUIUpdate, error: orgWebUIUpdateCheckError, } = requestManager.useCheckForWebUIUpdate({ notifyOnNetworkStatusChange: true }); const webUIUpdateCheckError = orgWebUIUpdateCheckError || webUIUpdateData?.checkForWebUIUpdate.tag === ''; const { data: webUIUpdateStatusData } = requestManager.useWebUIUpdateSubscription(); const { state: webUIUpdateState, progress: webUIUpdateProgress } = webUIUpdateStatusData?.webUIUpdateStatusChange ?? { state: UpdateState.Idle, progress: 0 }; useEffect(() => { const isError = webUIUpdateState === UpdateState.Error; if (isError) { makeToast(t('settings.about.webui.label.update_failure'), 'error'); } const updateFinished = webUIUpdateState === UpdateState.Finished; const resetUpdateStatus = isError || updateFinished; if (resetUpdateStatus) { requestManager.resetWebUIUpdateStatus().response.catch(() => {}); } if (!updateFinished) { return; } makeToast( t('settings.about.webui.label.update_success', { version: webUIUpdateStatusData!.webUIUpdateStatusChange.info.tag, }), 'success', ); requestManager.graphQLClient.client.cache.writeFragment({ fragment: ABOUT_WEBUI, data: { __typename: 'AboutWebUI', channel: webUIUpdateStatusData!.webUIUpdateStatusChange.info.channel, tag: webUIUpdateStatusData!.webUIUpdateStatusChange.info.tag, }, }); requestManager.graphQLClient.client.cache.writeFragment({ fragment: WEBUI_UPDATE_CHECK, data: { __typename: 'WebUIUpdateCheck', channel: webUIUpdateStatusData!.webUIUpdateStatusChange.info.channel, tag: webUIUpdateStatusData!.webUIUpdateStatusChange.info.tag, updateAvailable: false, }, }); }, [webUIUpdateState]); if (!aboutServer || !aboutWebUI) { return ; } const selectedServerChannelInfo = serverUpdateCheckData?.checkForServerUpdates?.find( (channel) => channel.channel === aboutServer.buildType, ); const isServerUpdateAvailable = !!selectedServerChannelInfo?.tag && selectedServerChannelInfo.tag !== getVersion(aboutServer); const isWebUIUpdateAvailable = !!webUIUpdateData?.checkForWebUIUpdate.updateAvailable; return ( {t('settings.server.title.server')} } > } /> {t('settings.webui.title.webui')} } > requestManager.updateWebUI().response.catch(() => {})} progress={webUIUpdateProgress} updateState={webUIUpdateState} /> } /> {t('global.label.links')} } > ); }