/*
* 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')}
}
>
);
}