Files
suwayomi-material-you-webui/src/modules/migration/screens/Migration.tsx

160 lines
6.2 KiB
TypeScript
Raw Normal View History

/*
* 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 { useMemo } from 'react';
import List from '@mui/material/List';
2024-09-30 04:01:23 +02:00
import Stack from '@mui/material/Stack';
import IconButton from '@mui/material/IconButton';
import SortByAlphaIcon from '@mui/icons-material/SortByAlpha';
import TagIcon from '@mui/icons-material/Tag';
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
import { CustomTooltip } from '@/modules/core/components/CustomTooltip.tsx';
import { requestManager } from '@/lib/requests/RequestManager.ts';
2025-05-03 12:14:05 +02:00
import { LoadingPlaceholder } from '@/modules/core/components/feedback/LoadingPlaceholder.tsx';
import { EmptyViewAbsoluteCentered } from '@/modules/core/components/feedback/EmptyViewAbsoluteCentered.tsx';
2024-10-05 21:35:25 +02:00
import { MigrationCard, TMigratableSource } from '@/modules/migration/components/MigrationCard.tsx';
2024-10-05 16:09:34 +02:00
import { StyledGroupItemWrapper } from '@/modules/core/components/virtuoso/StyledGroupItemWrapper.tsx';
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
2024-10-05 21:35:25 +02:00
import { SortBy, SortOrder, SortSettings, TMigratableSourcesResult } from '@/modules/migration/Migration.types.ts';
import { sortByToTranslationKey, sortOrderToTranslationKey } from '@/modules/migration/Migration.constants.ts';
2024-09-30 04:01:23 +02:00
import {
createUpdateMetadataServerSettings,
useMetadataServerSettings,
2024-10-05 22:53:22 +02:00
} from '@/modules/settings/services/ServerSettingsMetadata.ts';
2024-10-05 23:22:42 +02:00
import { makeToast } from '@/modules/core/utils/Toast.ts';
import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
2024-12-20 17:24:40 +01:00
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
2024-09-30 04:01:23 +02:00
const getMigratableSources = (
mangas: TMigratableSourcesResult | undefined,
{ sortBy, sortOrder }: SortSettings,
): TMigratableSource[] => {
if (!mangas) {
2024-09-30 04:01:23 +02:00
return [];
}
2024-09-30 04:01:23 +02:00
const sourceBySourceId: Record<string, TMigratableSource> = {};
mangas.forEach(({ sourceId, source }) => {
2024-09-30 04:01:23 +02:00
const uniqueSource = sourceBySourceId[sourceId] ?? {
...{ id: sourceId, name: sourceId, lang: 'unknown', iconUrl: null, mangaCount: 0, ...source },
};
2024-09-30 04:01:23 +02:00
sourceBySourceId[sourceId] = {
...uniqueSource,
mangaCount: uniqueSource.mangaCount + 1,
};
});
2024-09-30 04:01:23 +02:00
const sourcesSortedBy = Object.values(sourceBySourceId).toSorted((a, b) => {
switch (sortBy) {
case SortBy.SOURCE_NAME:
return a.name.localeCompare(b.name);
case SortBy.MANGA_COUNT:
return a.mangaCount - b.mangaCount;
default:
throw new Error(`Unexpected "sortBy" "${sortBy}"`);
}
});
switch (sortOrder) {
case SortOrder.ASC:
return sourcesSortedBy;
case SortOrder.DESC:
return sourcesSortedBy.toReversed();
default:
throw new Error(`Unexpected "sortOrder" "${sortOrder}"`);
}
};
2024-09-30 04:01:23 +02:00
export const Migration = ({ tabsMenuHeight }: { tabsMenuHeight: number }) => {
const { t } = useTranslation();
2024-09-30 04:01:23 +02:00
const { appBarHeight } = useNavBarContext();
const {
settings: { migrateSortSettings },
} = useMetadataServerSettings();
2024-12-20 17:24:40 +01:00
const updateMetadataServerSettings = createUpdateMetadataServerSettings<'migrateSortSettings'>((e) =>
makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
2024-09-30 04:01:23 +02:00
);
const { sortBy, sortOrder } = migrateSortSettings;
const { data, loading, error, refetch } = requestManager.useGetMigratableSources({
2024-04-27 22:28:55 +02:00
notifyOnNetworkStatusChange: true,
});
2024-09-30 04:01:23 +02:00
const migratableSources = useMemo(
() => getMigratableSources(data?.mangas.nodes, migrateSortSettings),
[data?.mangas.nodes, migrateSortSettings],
);
if (loading) {
return <LoadingPlaceholder />;
}
if (error) {
return (
2024-04-29 15:29:33 +02:00
<EmptyViewAbsoluteCentered
message={t('global.error.label.failed_to_load_data')}
2024-12-21 23:27:03 +01:00
messageExtra={getErrorMessage(error)}
retry={() => refetch().catch(defaultPromiseErrorHandler('Migration::refetch'))}
/>
);
}
return (
2024-09-30 04:01:23 +02:00
<>
<Stack
sx={{
position: 'sticky',
top: `${appBarHeight + tabsMenuHeight}px`,
flexDirection: 'row',
justifyContent: 'end',
alignItems: 'center',
gap: 1,
p: 1,
backgroundColor: 'background.default',
zIndex: 1,
}}
>
<CustomTooltip title={t(sortByToTranslationKey[sortBy])}>
2024-09-30 04:01:23 +02:00
<IconButton
color="inherit"
onClick={() =>
updateMetadataServerSettings('migrateSortSettings', { sortBy: (sortBy + 1) % 2, sortOrder })
}
>
{sortBy ? <TagIcon /> : <SortByAlphaIcon />}
</IconButton>
</CustomTooltip>
<CustomTooltip title={t(sortOrderToTranslationKey[sortOrder])}>
2024-09-30 04:01:23 +02:00
<IconButton
color="inherit"
onClick={() =>
updateMetadataServerSettings('migrateSortSettings', {
sortBy,
sortOrder: (sortOrder + 1) % 2,
})
}
>
{sortOrder ? <ArrowDownwardIcon /> : <ArrowUpwardIcon />}
</IconButton>
</CustomTooltip>
2024-09-30 04:01:23 +02:00
</Stack>
<List sx={{ p: 0 }}>
{migratableSources.map((migratableSource) => (
<StyledGroupItemWrapper key={migratableSource.id}>
<MigrationCard {...migratableSource} />
</StyledGroupItemWrapper>
))}
</List>
</>
);
};