/* * 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 from '@mui/material/Button'; import CircularProgress from '@mui/material/CircularProgress'; import Divider from '@mui/material/Divider'; import Stack from '@mui/material/Stack'; import Typography from '@mui/material/Typography'; 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 { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts'; 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(Number(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); return () => { setTitle(''); 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.useGetWebUIUpdateStatus(); const { state: webUIUpdateState, progress: webUIUpdateProgress } = webUIUpdateStatusData?.getWebUIUpdateStatus ?? { state: UpdateState.Idle, progress: 0, }; 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(defaultPromiseErrorHandler('About::updateWebUI')) } progress={webUIUpdateProgress} updateState={webUIUpdateState} /> } /> {t('global.label.links')} } > ); }