2025-12-06 12:59:39 -06:00
|
|
|
/*
|
|
|
|
|
* 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 List from '@mui/material/List';
|
|
|
|
|
import ListSubheader from '@mui/material/ListSubheader';
|
|
|
|
|
import ListItemText from '@mui/material/ListItemText';
|
|
|
|
|
import ListItemButton from '@mui/material/ListItemButton';
|
2026-01-10 19:45:02 +01:00
|
|
|
|
|
|
|
|
import { useLingui } from '@lingui/react/macro';
|
2025-12-06 12:59:39 -06:00
|
|
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
|
|
|
|
import { SelectSetting } from '@/base/components/settings/SelectSetting.tsx';
|
|
|
|
|
import { NumberSetting } from '@/base/components/settings/NumberSetting.tsx';
|
|
|
|
|
import { makeToast } from '@/base/utils/Toast.ts';
|
|
|
|
|
import { getErrorMessage, noOp } from '@/lib/HelperFunctions.ts';
|
|
|
|
|
import {
|
|
|
|
|
KoreaderSyncChecksumMethod,
|
|
|
|
|
KoreaderSyncConflictStrategy,
|
|
|
|
|
KoSyncStatusPayload,
|
|
|
|
|
} from '@/lib/graphql/generated/graphql.ts';
|
|
|
|
|
import {
|
|
|
|
|
KOREADER_SYNC_CHECKSUM_METHOD_SELECT_VALUES,
|
|
|
|
|
KOREADER_SYNC_CONFLICT_STRATEGY_SELECT_VALUES,
|
|
|
|
|
KOREADER_SYNC_PERCENTAGE_TOLERANCE,
|
|
|
|
|
} from '@/features/settings/Settings.constants.ts';
|
|
|
|
|
import { CredentialsLogin } from '@/base/components/modals/LoginDialog.tsx';
|
|
|
|
|
import { ServerSettings as ServerSettingsType, ServerSettings } from '@/features/settings/Settings.types.ts';
|
|
|
|
|
import { assertIsDefined } from '@/base/Asserts.ts';
|
|
|
|
|
|
|
|
|
|
export const KoreaderSyncSettings = ({
|
|
|
|
|
settings: {
|
|
|
|
|
koreaderSyncPercentageTolerance,
|
|
|
|
|
koreaderSyncStrategyBackward,
|
|
|
|
|
koreaderSyncStrategyForward,
|
|
|
|
|
koreaderSyncChecksumMethod,
|
|
|
|
|
},
|
|
|
|
|
serverAddress: currentServerAddress,
|
|
|
|
|
username: currentUsername,
|
|
|
|
|
isLoggedIn,
|
|
|
|
|
updateSetting,
|
|
|
|
|
}: {
|
|
|
|
|
settings: ServerSettings;
|
|
|
|
|
|
|
|
|
|
updateSetting: <Setting extends keyof ServerSettingsType>(
|
|
|
|
|
setting: Setting,
|
|
|
|
|
value: ServerSettingsType[Setting],
|
|
|
|
|
onCompletion?: (success: boolean) => void,
|
|
|
|
|
) => Promise<void>;
|
|
|
|
|
} & Omit<KoSyncStatusPayload, '__typename'>) => {
|
2026-01-10 19:45:02 +01:00
|
|
|
const { t } = useLingui();
|
2025-12-06 12:59:39 -06:00
|
|
|
|
|
|
|
|
const handleLogout = async () => {
|
|
|
|
|
const { data } = await requestManager.koSyncLogout().response;
|
|
|
|
|
|
|
|
|
|
if (data?.logoutKoSyncAccount.status.isLoggedIn) {
|
2026-01-10 19:45:02 +01:00
|
|
|
throw new Error(t`Could not log out from KOReader Sync`);
|
2025-12-06 12:59:39 -06:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleLogin = async (serverAddress: string, username: string, password: string) => {
|
|
|
|
|
const { data } = await requestManager.koSyncLogin(serverAddress, username, password).response;
|
|
|
|
|
|
|
|
|
|
if (!data?.connectKoSyncAccount.status.isLoggedIn) {
|
2026-01-10 19:45:02 +01:00
|
|
|
throw new Error(data?.connectKoSyncAccount.message ?? t`Unknown`);
|
2025-12-06 12:59:39 -06:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleLoginLogout = async (
|
|
|
|
|
initialServerAddress: string = 'https://sync.koreader.rocks/',
|
|
|
|
|
initialUsername?: string,
|
|
|
|
|
initialPassword?: string,
|
|
|
|
|
) => {
|
|
|
|
|
const controlled = CredentialsLogin.showControlled(
|
|
|
|
|
{
|
2026-01-10 19:45:02 +01:00
|
|
|
title: isLoggedIn ? t`Disconnect from KOReader Sync server` : t`Connect to KOReader Sync server`,
|
|
|
|
|
description: isLoggedIn ? t`Connected as ${initialUsername} to ${initialServerAddress}` : undefined,
|
2025-12-06 12:59:39 -06:00
|
|
|
isLoading: false,
|
|
|
|
|
isLoggedIn,
|
|
|
|
|
withServerAddress: true,
|
|
|
|
|
username: initialUsername,
|
|
|
|
|
password: initialPassword,
|
|
|
|
|
serverAddress: initialServerAddress,
|
|
|
|
|
loginLogout: async (username, password, serverAddress) => {
|
|
|
|
|
controlled.update({ isLoading: true, loginLogout: noOp });
|
|
|
|
|
|
|
|
|
|
if (isLoggedIn) {
|
|
|
|
|
try {
|
|
|
|
|
await handleLogout();
|
|
|
|
|
|
|
|
|
|
controlled.submit();
|
|
|
|
|
} catch (e) {
|
2026-01-10 19:45:02 +01:00
|
|
|
makeToast(t`Failed to disconnect from KOReader Sync server.`, 'error', getErrorMessage(e));
|
2025-12-06 12:59:39 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
assertIsDefined(serverAddress);
|
|
|
|
|
await handleLogin(serverAddress, username, password);
|
|
|
|
|
|
|
|
|
|
controlled.submit();
|
|
|
|
|
} catch (e) {
|
2026-01-10 19:45:02 +01:00
|
|
|
makeToast(t`Failed to connect to KOReader Sync server.`, 'error', getErrorMessage(e));
|
2025-12-06 12:59:39 -06:00
|
|
|
|
|
|
|
|
const RETRY_KEY = '__retry__';
|
|
|
|
|
const retry = await Promise.race([controlled.promise, Promise.resolve(RETRY_KEY)]);
|
|
|
|
|
if (retry === RETRY_KEY) {
|
|
|
|
|
handleLoginLogout(serverAddress, username, password);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{ id: 'koreader-sync-login-logout' },
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<List
|
|
|
|
|
subheader={
|
|
|
|
|
<ListSubheader component="div" id="koreader-sync-settings">
|
2026-01-10 19:45:02 +01:00
|
|
|
{t`KOReader Sync`}
|
2025-12-06 12:59:39 -06:00
|
|
|
</ListSubheader>
|
|
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
<ListItemButton
|
|
|
|
|
onClick={() => handleLoginLogout(currentServerAddress ?? undefined, currentUsername ?? undefined)}
|
|
|
|
|
>
|
|
|
|
|
<ListItemText
|
2026-01-10 19:45:02 +01:00
|
|
|
primary={t`Sync status`}
|
2025-12-06 12:59:39 -06:00
|
|
|
secondary={
|
2026-01-10 19:45:02 +01:00
|
|
|
isLoggedIn ? t`Connected as ${currentUsername} to ${currentServerAddress}` : t`Disconnected`
|
2025-12-06 12:59:39 -06:00
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
</ListItemButton>
|
|
|
|
|
<SelectSetting<KoreaderSyncConflictStrategy>
|
2026-01-10 19:45:02 +01:00
|
|
|
settingName={t`Sync to a newer state`}
|
2025-12-06 12:59:39 -06:00
|
|
|
value={koreaderSyncStrategyForward}
|
|
|
|
|
values={KOREADER_SYNC_CONFLICT_STRATEGY_SELECT_VALUES}
|
|
|
|
|
handleChange={(value) => updateSetting('koreaderSyncStrategyForward', value)}
|
|
|
|
|
/>
|
|
|
|
|
<SelectSetting<KoreaderSyncConflictStrategy>
|
2026-01-10 19:45:02 +01:00
|
|
|
settingName={t`Sync to an older state`}
|
2025-12-06 12:59:39 -06:00
|
|
|
value={koreaderSyncStrategyBackward}
|
|
|
|
|
values={KOREADER_SYNC_CONFLICT_STRATEGY_SELECT_VALUES}
|
|
|
|
|
handleChange={(value) => updateSetting('koreaderSyncStrategyBackward', value)}
|
|
|
|
|
/>
|
|
|
|
|
<SelectSetting<KoreaderSyncChecksumMethod>
|
2026-01-10 19:45:02 +01:00
|
|
|
settingName={t`Document matching method`}
|
2025-12-06 12:59:39 -06:00
|
|
|
value={koreaderSyncChecksumMethod}
|
|
|
|
|
values={KOREADER_SYNC_CHECKSUM_METHOD_SELECT_VALUES}
|
|
|
|
|
handleChange={(value) => updateSetting('koreaderSyncChecksumMethod', value)}
|
|
|
|
|
/>
|
|
|
|
|
<NumberSetting
|
2026-01-10 19:45:02 +01:00
|
|
|
settingTitle={t`Percentage Tolerance`}
|
|
|
|
|
dialogDescription={t`Sync will not be triggered if the progress difference is within this tolerance.`}
|
2025-12-06 12:59:39 -06:00
|
|
|
settingValue={koreaderSyncPercentageTolerance.toString()}
|
|
|
|
|
value={koreaderSyncPercentageTolerance}
|
|
|
|
|
defaultValue={KOREADER_SYNC_PERCENTAGE_TOLERANCE.default}
|
|
|
|
|
minValue={KOREADER_SYNC_PERCENTAGE_TOLERANCE.min}
|
|
|
|
|
maxValue={KOREADER_SYNC_PERCENTAGE_TOLERANCE.max}
|
|
|
|
|
stepSize={KOREADER_SYNC_PERCENTAGE_TOLERANCE.step}
|
|
|
|
|
valueUnit=""
|
|
|
|
|
handleUpdate={(value) => updateSetting('koreaderSyncPercentageTolerance', value)}
|
|
|
|
|
/>
|
|
|
|
|
</List>
|
|
|
|
|
);
|
|
|
|
|
};
|