/*
* 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 { useCallback, useMemo } from 'react';
import IconButton from '@mui/material/IconButton';
import TravelExploreIcon from '@mui/icons-material/TravelExplore';
import { useNavigate } from 'react-router-dom';
import Typography from '@mui/material/Typography';
import { useLingui } from '@lingui/react/macro';
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import { DefaultLanguage } from '@/base/utils/Languages.ts';
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
import { SourceCard } from '@/features/browse/sources/components/SourceCard.tsx';
import { EmptyViewAbsoluteCentered } from '@/base/components/feedback/EmptyViewAbsoluteCentered.tsx';
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import { isPinnedOrLastUsedSource, translateExtensionLanguage } from '@/features/extension/Extensions.utils.ts';
import { AppRoutes } from '@/base/AppRoute.constants.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
import { Sources as SourceService } from '@/features/source/services/Sources.ts';
import { useMetadataServerSettings } from '@/features/settings/services/ServerSettingsMetadata.ts';
import { useAppAction } from '@/features/navigation-bar/hooks/useAppAction.ts';
import { StyledGroupedVirtuoso } from '@/base/components/virtuoso/StyledGroupedVirtuoso.tsx';
import { VirtuosoUtil } from '@/lib/virtuoso/Virtuoso.util.tsx';
import { StyledGroupHeader } from '@/base/components/virtuoso/StyledGroupHeader.tsx';
import { StyledGroupItemWrapper } from '@/base/components/virtuoso/StyledGroupItemWrapper.tsx';
import { SourceLanguageSelect } from '@/features/source/components/SourceLanguageSelect.tsx';
export function Sources({ tabsMenuHeight }: { tabsMenuHeight: number }) {
const { t } = useLingui();
const { languages: shownLangs, setLanguages: setShownLangs } = SourceService.useLanguages();
const {
settings: { showNsfw, lastUsedSourceId },
} = useMetadataServerSettings();
const {
data,
loading: isLoading,
error,
refetch,
} = requestManager.useGetSourceList({ notifyOnNetworkStatusChange: true });
const sources = data?.sources.nodes;
const filteredSources = useMemo(
() =>
SourceService.filter(sources ?? [], {
showNsfw,
languages: shownLangs,
keepLocalSource: true,
enabled: true,
}),
[sources, shownLangs],
);
const sourcesForLanguageFilter = useMemo(
() =>
SourceService.filter(sources ?? [], {
showNsfw,
keepLocalSource: false,
}),
[sources],
);
const sourcesByLanguage = useMemo(() => {
const lastUsedSource = SourceService.getLastUsedSource(lastUsedSourceId, filteredSources);
const groupedByLanguageTuple = Object.entries(
SourceService.groupByLanguage(filteredSources, { withPinnedGroup: true }),
);
if (lastUsedSource) {
return [
[DefaultLanguage.LAST_USED_SOURCE, [lastUsedSource]],
...groupedByLanguageTuple,
] satisfies typeof groupedByLanguageTuple;
}
return groupedByLanguageTuple;
}, [filteredSources]);
const sourceLanguages = useMemo(
() => SourceService.getLanguages(sourcesForLanguageFilter, { excludeLocalSource: true }),
[sources],
);
const areSourcesFromDifferentRepos = useMemo(
() => SourceService.areFromMultipleRepos(filteredSources),
[filteredSources],
);
const visibleSources = useMemo(
() => sourcesByLanguage.map(([, sourcesOfLanguage]) => sourcesOfLanguage).flat(1),
[sourcesByLanguage],
);
const groupCounts = useMemo(
() => sourcesByLanguage.map((sourceGroup) => sourceGroup[1].length),
[sourcesByLanguage],
);
const computeItemKey = VirtuosoUtil.useCreateGroupedComputeItemKey(
groupCounts,
useCallback((index) => sourcesByLanguage[index][0], [sourcesByLanguage]),
useCallback(
(index, groupIndex) => `${sourcesByLanguage[groupIndex][0]}_${visibleSources[index].id}`,
[visibleSources],
),
);
const navigate = useNavigate();
useAppAction(
<>
navigate(AppRoutes.sources.childRoutes.searchAll.path())} color="inherit">
>,
[t, shownLangs, sourceLanguages, sourcesForLanguageFilter],
);
if (isLoading) return ;
if (error) {
return (
refetch().catch(defaultPromiseErrorHandler('Sources::refetch'))}
/>
);
}
if (sources?.length === 0) {
return ;
}
return (
{
const [language] = sourcesByLanguage[index];
return (
{translateExtensionLanguage(language)}
);
}}
itemContent={(index, groupIndex) => {
const language = sourcesByLanguage[groupIndex][0];
const source = visibleSources[index];
return (
);
}}
/>
);
}