Files
suwayomi-material-you-webui/src/modules/tracker/components/TrackManga.tsx

151 lines
6.0 KiB
TypeScript
Raw Normal View History

2024-03-10 17:36:22 +01: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 { useNavigate } from 'react-router-dom';
import Box from '@mui/material/Box';
import { useEffect, useMemo, useRef, useState } from 'react';
2024-03-10 17:36:22 +01:00
import DialogContent from '@mui/material/DialogContent';
import { useTranslation } from 'react-i18next';
import { requestManager } from '@/lib/requests/RequestManager.ts';
2025-05-03 12:14:05 +02:00
import { LoadingPlaceholder } from '@/modules/core/components/feedback/LoadingPlaceholder.tsx';
2024-10-05 21:32:31 +02:00
import { Trackers } from '@/modules/tracker/services/Trackers.ts';
import { TrackerCard, TrackerMode } from '@/modules/tracker/components/cards/TrackerCard.tsx';
2024-10-05 23:22:42 +02:00
import { makeToast } from '@/modules/core/utils/Toast.ts';
2024-10-05 16:09:34 +02:00
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
2024-07-08 18:02:50 +02:00
import { GetMangaTrackRecordsQuery, GetTrackersBindQuery, MangaType } from '@/lib/graphql/generated/graphql.ts';
import { GET_TRACKERS_BIND } from '@/lib/graphql/queries/TrackerQuery.ts';
2024-07-08 18:02:50 +02:00
import { GET_MANGA_TRACK_RECORDS } from '@/lib/graphql/queries/MangaQuery.ts';
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
2024-12-11 20:10:59 +01:00
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
2024-12-20 17:24:40 +01:00
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
2025-05-03 12:14:05 +02:00
import { EmptyView } from '@/modules/core/components/feedback/EmptyView.tsx';
2024-03-10 17:36:22 +01:00
const getTrackerMode = (id: number, trackersInUse: number[], searchModeForTracker?: number): TrackerMode => {
if (id === searchModeForTracker) {
return TrackerMode.SEARCH;
}
if (trackersInUse.includes(id)) {
return TrackerMode.INFO;
}
return TrackerMode.UNTRACKED;
};
2024-07-08 18:02:50 +02:00
export const TrackManga = ({ manga }: { manga: MangaIdInfo & Pick<MangaType, 'title'> }) => {
const { t } = useTranslation();
2024-03-10 17:36:22 +01:00
const navigate = useNavigate();
const [searchModeForTracker, setSearchModeForTracker] = useState<number>();
const trackerList = requestManager.useGetTrackerList<GetTrackersBindQuery>(GET_TRACKERS_BIND, {
notifyOnNetworkStatusChange: true,
});
2024-07-08 18:02:50 +02:00
const trackers = trackerList.data?.trackers.nodes ?? [];
2024-03-10 17:36:22 +01:00
2024-07-08 18:02:50 +02:00
const mangaTrackRecordsList = requestManager.useGetManga<GetMangaTrackRecordsQuery>(
GET_MANGA_TRACK_RECORDS,
manga.id,
);
const mangaTrackRecords = mangaTrackRecordsList.data?.manga.trackRecords.nodes ?? [];
const loggedInTrackers = Trackers.getLoggedIn(trackers);
const trackersInUse = Trackers.getLoggedIn(Trackers.getTrackers(mangaTrackRecords, trackers));
2024-03-10 17:36:22 +01:00
const trackersInUseIds = Trackers.getIds(trackersInUse);
const isSearchActive = searchModeForTracker !== undefined;
const OptionalDialogContent = useMemo(() => (isSearchActive ? Box : DialogContent), [isSearchActive]);
const loading = trackerList.loading || mangaTrackRecordsList.loading;
const error = trackerList.error ?? mangaTrackRecordsList.error;
useEffect(() => {
if (!loading && !error && !trackersInUse.length && !loggedInTrackers.length) {
2024-12-11 20:10:59 +01:00
navigate(AppRoutes.tracker.path);
}
}, [loading]);
const fetchedLatestTrackDataRef = useRef(false);
useEffect(() => {
if (!mangaTrackRecords.length || fetchedLatestTrackDataRef.current) {
return;
}
fetchedLatestTrackDataRef.current = true;
Promise.all(
mangaTrackRecords.map((trackRecord) => requestManager.fetchTrackBind(trackRecord.id).response),
2024-12-20 17:24:40 +01:00
).catch((e) => makeToast(t('tracking.error.label.could_not_fetch_track_info'), 'error', getErrorMessage(e)));
2024-07-08 18:02:50 +02:00
}, [mangaTrackRecords]);
2024-03-10 17:36:22 +01:00
const trackerComponents = useMemo(
() =>
loggedInTrackers.map((tracker) => {
const mode = getTrackerMode(tracker.id, trackersInUseIds, searchModeForTracker);
2024-07-08 18:02:50 +02:00
const trackRecord = Trackers.getTrackRecordFor(tracker, mangaTrackRecords);
2024-03-10 17:36:22 +01:00
const isSearchForTracker = mode === TrackerMode.SEARCH;
if (isSearchActive && !isSearchForTracker) {
return null;
}
return (
<TrackerCard
key={tracker.id}
tracker={tracker}
2024-07-08 18:02:50 +02:00
manga={manga}
2024-03-10 17:36:22 +01:00
trackRecord={trackRecord}
mode={mode}
setSearchMode={(id) => setSearchModeForTracker(id)}
/>
);
}),
2024-07-08 18:02:50 +02:00
[trackersInUseIds, searchModeForTracker, mangaTrackRecords],
2024-03-10 17:36:22 +01:00
);
2024-07-08 18:02:50 +02:00
if (error) {
return (
2025-03-28 18:23:33 +01:00
<EmptyView
message={t('global.error.label.failed_to_load_data')}
2024-12-21 23:27:03 +01:00
messageExtra={getErrorMessage(error)}
2024-07-08 18:02:50 +02:00
retry={() => {
if (trackerList.error) {
trackerList.refetch().catch(defaultPromiseErrorHandler('TrackManga::refetch: trackerList'));
}
if (mangaTrackRecordsList.error) {
mangaTrackRecordsList
.refetch()
.catch(defaultPromiseErrorHandler('TrackManga::refetch: mangaTrackRecordsList'));
}
}}
/>
);
2024-03-10 17:36:22 +01:00
}
2024-07-08 18:02:50 +02:00
if (loading) {
2024-03-10 17:36:22 +01:00
return <LoadingPlaceholder />;
}
if (!isSearchActive) {
return (
<OptionalDialogContent
sx={{
padding: 0,
// MUI adds a bottom padding to the last child of type CardContent which can only be removed via actual css styling
// do it here, so it is done in one place for all track related CardContent components
'.MuiPaper-root .MuiCardContent-root': { paddingBottom: '0' },
}}
>
2024-03-10 17:36:22 +01:00
{trackerComponents}
</OptionalDialogContent>
);
}
return trackerComponents;
};