Update KOReader Sync settings (#1030)
* feat(settings): refactor and implement KOReader Sync settings page Refactors the KOReader Sync settings section into its own dedicated page and implements the full connection and configuration flow. The previous implementation in the main server settings was outdated and incomplete. This change aligns the frontend with the latest backend logic and provides a complete user experience. Key changes include: - Separated the UI into two states: a login form for initial connection and a configuration panel for existing connections. - Implemented `connectKoSyncAccount` and `logoutKoSyncAccount` mutations to handle authentication with the KOReader Sync server. - Updated the `SERVER_SETTINGS` GraphQL fragment to use the new `koreaderSyncStrategyForward` and `koreaderSyncStrategyBackward` fields, removing the deprecated `koreaderSyncStrategy`. - Moved all KOReader Sync settings from the generic Server Settings page to a dedicated route at `/settings/koreader-sync`. - Restructured and improved i18n keys for better clarity and consistency. Ran `yarn gql:codegen` to regenerate GraphQL types and helpers. * Extract credentials login into component * Update to server changes and cleanup --------- Co-authored-by: schroda <50052685+schroda@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* 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 { useTranslation } from 'react-i18next';
|
||||
import List from '@mui/material/List';
|
||||
import ListSubheader from '@mui/material/ListSubheader';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
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'>) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleLogout = async () => {
|
||||
const { data } = await requestManager.koSyncLogout().response;
|
||||
|
||||
if (data?.logoutKoSyncAccount.status.isLoggedIn) {
|
||||
throw new Error(
|
||||
t('tracking.action.logout.label.failure', { name: t('settings.server.koreader.sync.title') }),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogin = async (serverAddress: string, username: string, password: string) => {
|
||||
const { data } = await requestManager.koSyncLogin(serverAddress, username, password).response;
|
||||
|
||||
if (!data?.connectKoSyncAccount.status.isLoggedIn) {
|
||||
throw new Error(data?.connectKoSyncAccount.message ?? t('global.label.unknown'));
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoginLogout = async (
|
||||
initialServerAddress: string = 'https://sync.koreader.rocks/',
|
||||
initialUsername?: string,
|
||||
initialPassword?: string,
|
||||
) => {
|
||||
const controlled = CredentialsLogin.showControlled(
|
||||
{
|
||||
title: t(
|
||||
isLoggedIn
|
||||
? 'settings.server.koreader.sync.connection.disconnect'
|
||||
: 'settings.server.koreader.sync.connection.connect',
|
||||
{
|
||||
name: t('settings.server.koreader.sync.title'),
|
||||
},
|
||||
),
|
||||
description: isLoggedIn
|
||||
? t('settings.server.koreader.sync.connection.connected', {
|
||||
username: initialUsername,
|
||||
serverAddress: initialServerAddress,
|
||||
})
|
||||
: undefined,
|
||||
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) {
|
||||
makeToast(
|
||||
t('settings.server.koreader.sync.connection.message.disconnect_error'),
|
||||
'error',
|
||||
getErrorMessage(e),
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
assertIsDefined(serverAddress);
|
||||
await handleLogin(serverAddress, username, password);
|
||||
|
||||
controlled.submit();
|
||||
} catch (e) {
|
||||
makeToast(
|
||||
t('settings.server.koreader.sync.connection.message.connect_error'),
|
||||
'error',
|
||||
getErrorMessage(e),
|
||||
);
|
||||
|
||||
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">
|
||||
{t('settings.server.koreader.sync.title')}
|
||||
</ListSubheader>
|
||||
}
|
||||
>
|
||||
<ListItemButton
|
||||
onClick={() => handleLoginLogout(currentServerAddress ?? undefined, currentUsername ?? undefined)}
|
||||
>
|
||||
<ListItemText
|
||||
primary={t('settings.server.koreader.sync.connection.status')}
|
||||
secondary={
|
||||
isLoggedIn
|
||||
? t('settings.server.koreader.sync.connection.connected', {
|
||||
username: currentUsername,
|
||||
serverAddress: currentServerAddress,
|
||||
})
|
||||
: t('settings.server.koreader.sync.connection.disconnected')
|
||||
}
|
||||
/>
|
||||
</ListItemButton>
|
||||
|
||||
<SelectSetting<KoreaderSyncConflictStrategy>
|
||||
settingName={t('settings.server.koreader.sync.strategy.forward_title')}
|
||||
value={koreaderSyncStrategyForward}
|
||||
values={KOREADER_SYNC_CONFLICT_STRATEGY_SELECT_VALUES}
|
||||
handleChange={(value) => updateSetting('koreaderSyncStrategyForward', value)}
|
||||
/>
|
||||
|
||||
<SelectSetting<KoreaderSyncConflictStrategy>
|
||||
settingName={t('settings.server.koreader.sync.strategy.backward_title')}
|
||||
value={koreaderSyncStrategyBackward}
|
||||
values={KOREADER_SYNC_CONFLICT_STRATEGY_SELECT_VALUES}
|
||||
handleChange={(value) => updateSetting('koreaderSyncStrategyBackward', value)}
|
||||
/>
|
||||
|
||||
<SelectSetting<KoreaderSyncChecksumMethod>
|
||||
settingName={t('settings.server.koreader.sync.check_sum_method.title')}
|
||||
value={koreaderSyncChecksumMethod}
|
||||
values={KOREADER_SYNC_CHECKSUM_METHOD_SELECT_VALUES}
|
||||
handleChange={(value) => updateSetting('koreaderSyncChecksumMethod', value)}
|
||||
/>
|
||||
|
||||
<NumberSetting
|
||||
settingTitle={t('settings.server.koreader.sync.tolerance.title')}
|
||||
dialogDescription={t('settings.server.koreader.sync.tolerance.description')}
|
||||
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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user