2021-03-26 04:17:02 +04:30
/ *
* Copyright ( C ) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
2021-01-26 23:32:12 +03:30
* License , v . 2.0 . If a copy of the MPL was not distributed with this
2023-05-18 13:17:41 +02:00
* file , You can obtain one at https : //mozilla.org/MPL/2.0/.
* /
2021-01-26 23:32:12 +03:30
2025-05-03 13:24:55 +02:00
import { useCallback , useEffect , useMemo , useRef , useState } from 'react' ;
2024-12-27 04:11:02 +01:00
import { useParams , useNavigate , useLocation , useSearchParams } from 'react-router-dom' ;
2021-09-09 22:57:19 +04:30
import IconButton from '@mui/material/IconButton' ;
import SettingsIcon from '@mui/icons-material/Settings' ;
2022-03-04 01:25:10 +00:00
import { useQueryParam , StringParam } from 'use-query-params' ;
2023-03-23 13:59:32 +01:00
import { useTranslation } from 'react-i18next' ;
2023-05-12 11:52:00 +02:00
import Link from '@mui/material/Link' ;
2024-04-14 15:50:12 +02:00
import Box from '@mui/material/Box' ;
import Button from '@mui/material/Button' ;
import { styled } from '@mui/material/styles' ;
2023-05-28 12:16:06 +02:00
import FavoriteIcon from '@mui/icons-material/Favorite' ;
import NewReleasesIcon from '@mui/icons-material/NewReleases' ;
import FilterListIcon from '@mui/icons-material/FilterList' ;
2025-02-01 14:24:38 +01:00
import { CustomTooltip } from '@/modules/core/components/CustomTooltip.tsx' ;
2023-11-11 23:43:22 +01:00
import {
requestManager ,
AbortableApolloUseMutationPaginatedResponse ,
SPECIAL_ED_SOURCES ,
2024-10-06 01:27:51 +02:00
} from '@/lib/requests/RequestManager.ts' ;
2024-10-05 21:22:12 +02:00
import { SourceGridLayout } from '@/modules/source/components/SourceGridLayout.tsx' ;
2024-10-05 16:09:34 +02:00
import { AppbarSearch } from '@/modules/core/components/AppbarSearch.tsx' ;
2024-10-05 21:22:12 +02:00
import { SourceOptions } from '@/modules/source/components/SourceOptions.tsx' ;
import { BaseMangaGrid } from '@/modules/manga/components/BaseMangaGrid.tsx' ;
2023-09-12 02:36:11 +02:00
import {
2024-07-03 23:15:50 +02:00
GetSourceBrowseQuery ,
GetSourceBrowseQueryVariables ,
2023-09-12 02:36:11 +02:00
GetSourceMangasFetchMutation ,
GetSourceMangasFetchMutationVariables ,
} from '@/lib/graphql/generated/graphql.ts' ;
2025-05-17 23:37:00 +02:00
import {
updateMetadataServerSettings ,
useMetadataServerSettings ,
} from '@/modules/settings/services/ServerSettingsMetadata.ts' ;
2024-10-05 16:09:34 +02:00
import { useLocalStorage , useSessionStorage } from '@/modules/core/hooks/useStorage.tsx' ;
2025-05-25 15:36:25 +02:00
import { MANGA_GRID_SNAPSHOT_KEY } from '@/modules/manga/components/MangaGrid.tsx' ;
2024-10-26 17:25:04 +02:00
import { createUpdateSourceMetadata , useGetSourceMetadata } from '@/modules/source/services/SourceMetadata.ts' ;
2024-10-05 23:22:42 +02:00
import { makeToast } from '@/modules/core/utils/Toast.ts' ;
2024-07-03 23:15:50 +02:00
import { GET_SOURCE_BROWSE } from '@/lib/graphql/queries/SourceQuery.ts' ;
2024-10-05 16:09:34 +02:00
import { TranslationKey } from '@/Base.types.ts' ;
2025-04-21 17:00:13 +02:00
import { IPos , SourceIdInfo } from '@/modules/source/Source.types.ts' ;
2024-10-06 16:15:01 +02:00
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts' ;
2025-05-03 12:14:05 +02:00
import { EmptyView } from '@/modules/core/components/feedback/EmptyView.tsx' ;
import { EmptyViewAbsoluteCentered } from '@/modules/core/components/feedback/EmptyViewAbsoluteCentered.tsx' ;
2024-10-07 13:35:07 +02:00
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts' ;
2025-07-23 01:52:41 +02:00
import { GridLayout , SearchParam } from '@/modules/core/Core.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-04-21 17:00:13 +02:00
import { Sources } from '@/modules/source/services/Sources.ts' ;
2025-05-03 13:37:20 +02:00
import { useAppTitleAndAction } from '@/modules/navigation-bar/hooks/useAppTitleAndAction.ts' ;
2025-05-03 13:24:55 +02:00
import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx' ;
2025-05-25 15:36:25 +02:00
import { VirtuosoUtil } from '@/lib/virtuoso/Virtuoso.util.tsx' ;
2024-10-26 17:25:04 +02:00
2025-04-21 17:00:13 +02:00
const DEFAULT_SOURCE : SourceIdInfo = { id : '-1' } ;
2023-05-28 12:16:06 +02:00
const ContentTypeMenu = styled ( 'div' ) ( ( { theme } ) = > ( {
display : 'flex' ,
2024-09-14 03:01:07 +02:00
position : 'sticky' ,
2023-05-28 12:16:06 +02:00
width : '100%' ,
zIndex : 1 ,
2024-09-14 03:01:07 +02:00
padding : theme.spacing ( 1 ) ,
gap : theme.spacing ( 1 ) ,
2023-05-28 12:16:06 +02:00
backgroundColor : theme.palette.background.default ,
} ) ) ;
2024-09-14 03:01:07 +02:00
const ContentTypeButton = styled ( Button ) ( ( ) = > ( { } ) ) ;
2023-05-28 12:16:06 +02:00
2024-09-14 03:01:07 +02:00
const StyledGridWrapper = styled ( Box ) ( ( ) = > ( {
minHeight : '100%' ,
position : 'relative' ,
} ) ) ;
2023-05-28 12:16:06 +02:00
export enum SourceContentType {
POPULAR ,
LATEST ,
SEARCH ,
}
2022-03-04 01:25:10 +00:00
2023-05-28 12:16:06 +02:00
const SOURCE_CONTENT_TYPE_TO_ERROR_MSG_KEY : { [ contentType in SourceContentType ] : TranslationKey } = {
[ SourceContentType . POPULAR ] : 'manga.error.label.no_mangas_found' ,
[ SourceContentType . LATEST ] : 'manga.error.label.no_mangas_found' ,
2024-01-11 02:57:56 +01:00
[ SourceContentType . SEARCH ] : 'manga.error.label.no_matches' ,
2023-05-28 12:16:06 +02:00
} ;
2024-07-08 18:02:50 +02:00
const getUniqueMangas = < Manga extends MangaIdInfo > ( mangas : Manga [ ] ) : Manga [ ] = > {
const mangaIdToManga : Record < string , Manga > = { } ;
const uniqueMangas : Manga [ ] = [ ] ;
2023-05-28 12:16:06 +02:00
mangas . forEach ( ( manga ) = > {
2024-05-05 23:27:52 +02:00
const isDuplicate = ! ! mangaIdToManga [ manga . id ] ;
2023-05-28 12:16:06 +02:00
if ( ! isDuplicate ) {
2024-05-05 23:27:52 +02:00
mangaIdToManga [ manga . id ] = manga ;
2023-05-28 12:16:06 +02:00
uniqueMangas . push ( manga ) ;
}
} ) ;
return uniqueMangas ;
} ;
const useSourceManga = (
sourceId : string ,
contentType : SourceContentType ,
searchTerm : string | null | undefined ,
filters : IPos [ ] ,
2023-09-12 02:36:11 +02:00
initialPages : number ,
2024-03-23 23:44:46 +01:00
hideLibraryEntries : boolean ,
2023-09-12 02:36:11 +02:00
) : [
AbortableApolloUseMutationPaginatedResponse < GetSourceMangasFetchMutation , GetSourceMangasFetchMutationVariables > [ 0 ] ,
AbortableApolloUseMutationPaginatedResponse <
GetSourceMangasFetchMutation ,
GetSourceMangasFetchMutationVariables
2024-03-23 23:44:46 +01:00
> [ 1 ] [ number ] & { filteredOutAllItemsOfFetchedPage : boolean } ,
2023-09-12 02:36:11 +02:00
] = > {
let result : AbortableApolloUseMutationPaginatedResponse <
GetSourceMangasFetchMutation ,
GetSourceMangasFetchMutationVariables
> ;
2023-05-28 12:16:06 +02:00
switch ( contentType ) {
case SourceContentType . POPULAR :
2023-06-17 16:50:17 +02:00
result = requestManager . useGetSourcePopularMangas ( sourceId , initialPages ) ;
2023-05-28 12:16:06 +02:00
break ;
case SourceContentType . LATEST :
2023-06-17 16:50:17 +02:00
result = requestManager . useGetSourceLatestMangas ( sourceId , initialPages ) ;
2023-05-28 12:16:06 +02:00
break ;
case SourceContentType . SEARCH :
2023-09-24 21:32:23 +02:00
result = requestManager . useSourceSearch (
sourceId ,
2024-01-11 02:57:56 +01:00
searchTerm ? ? '' ,
2023-09-24 21:32:23 +02:00
filters . map ( ( filter ) = > {
const { position , state , group } = filter ;
const isPartOfGroup = group !== undefined ;
if ( isPartOfGroup ) {
return {
position : group ,
groupChange : {
position ,
[ filter . type ] : state ,
} ,
} ;
}
return {
position ,
[ filter . type ] : state ,
} ;
} ) ,
initialPages ,
) ;
2023-05-28 12:16:06 +02:00
break ;
default :
throw new Error ( ` Unknown ContentType " ${ contentType } " ` ) ;
}
2023-09-12 02:36:11 +02:00
const pages = result [ 1 ] ! ;
const lastLoadedPageIndex = pages . findLastIndex ( ( page ) = > ! ! page . data ? . fetchSourceManga ) ;
const lastLoadedPage = pages [ lastLoadedPageIndex ] ;
2023-05-28 12:16:06 +02:00
2024-03-23 23:44:46 +01:00
const isPageLoading = pages . slice ( - 1 ) [ 0 ] . isLoading ;
let filteredOutAllItemsOfFetchedPage = ! isPageLoading ;
const items = useMemo ( ( ) = > {
2024-06-02 15:24:29 +02:00
type FetchItemsResult = NonNullable < GetSourceMangasFetchMutation [ 'fetchSourceManga' ] > [ 'mangas' ] ;
2024-03-23 23:44:46 +01:00
let allItems : FetchItemsResult = [ ] ;
pages . forEach ( ( page , index ) = > {
2024-07-11 17:55:00 +02:00
const pageItems = page . data ? . fetchSourceManga ? . mangas ? ? [ ] ;
2024-05-05 23:27:52 +02:00
const nonLibraryPageItems = pageItems . filter ( ( item ) = > ! hideLibraryEntries || ! item . inLibrary ) ;
const uniqueItems = getUniqueMangas ( [ . . . allItems , . . . nonLibraryPageItems ] ) ;
2024-03-23 23:44:46 +01:00
const isLastPage = ! isPageLoading && pages . length === index + 1 ;
2024-05-05 23:27:52 +02:00
filteredOutAllItemsOfFetchedPage = isLastPage && ! nonLibraryPageItems . length && ! ! pageItems . length ;
allItems = uniqueItems ;
2024-03-23 23:44:46 +01:00
} ) ;
return allItems ;
} , [ pages , hideLibraryEntries ] ) ;
if ( lastLoadedPageIndex === - 1 ) {
return [ result [ 0 ] , { . . . result [ 1 ] [ result [ 1 ] . length - 1 ] , filteredOutAllItemsOfFetchedPage } ] ;
2023-09-12 02:36:11 +02:00
}
return [
result [ 0 ] ,
{
. . . pages [ pages . length - 1 ] ,
data : {
. . . lastLoadedPage ! . data ,
fetchSourceManga : {
. . . lastLoadedPage ! . data ! . fetchSourceManga ,
hasNextPage :
pages . length > lastLoadedPageIndex + 1
? false
2024-06-02 15:24:29 +02:00
: ! ! lastLoadedPage ! . data ! . fetchSourceManga ? . hasNextPage ,
2024-03-23 23:44:46 +01:00
mangas : items ,
2023-09-12 02:36:11 +02:00
} ,
} ,
2024-03-23 23:44:46 +01:00
filteredOutAllItemsOfFetchedPage ,
2023-09-12 02:36:11 +02:00
} ,
] ;
2023-05-28 12:16:06 +02:00
} ;
2023-10-28 00:32:02 +02:00
export function SourceMangas() {
2023-03-23 13:59:32 +01:00
const { t } = useTranslation ( ) ;
2025-05-03 13:24:55 +02:00
const { appBarHeight } = useNavBarContext ( ) ;
2023-11-12 03:21:26 +01:00
2021-05-13 17:46:40 +04:30
const { sourceId } = useParams < { sourceId : string } > ( ) ;
2023-05-18 13:25:19 +02:00
2024-12-27 04:11:02 +01:00
const [ searchParams , setSearchParams ] = useSearchParams ( ) ;
2023-06-04 21:30:15 +02:00
const navigate = useNavigate ( ) ;
2024-04-16 02:49:08 +02:00
const location = useLocation ( ) ;
const { key : locationKey , state : locationState } = location ;
2024-04-07 15:22:03 +02:00
const { contentType : initialContentType = SourceContentType . POPULAR , clearCache = false } =
useLocation < {
contentType : SourceContentType ;
clearCache : boolean ;
} > ( ) . state ? ? { } ;
2023-11-12 03:21:26 +01:00
2024-03-23 23:44:46 +01:00
const {
settings : { hideLibraryEntries } ,
} = useMetadataServerSettings ( ) ;
2024-12-27 04:53:25 +01:00
2024-09-17 03:40:55 +02:00
const [ sourceGridLayout ] = useLocalStorage ( 'source-grid-layout' , GridLayout . Compact ) ;
2025-07-23 01:52:41 +02:00
const [ query ] = useQueryParam ( SearchParam . QUERY , StringParam ) ;
2024-04-14 15:18:51 +02:00
const [ currentFiltersToApply , setCurrentFiltersToApply ] = useSessionStorage < IPos [ ] | undefined > (
` source-mangas- ${ sourceId } -filters ` ,
2024-04-07 15:22:03 +02:00
[ ] ,
) ;
2024-04-14 15:18:51 +02:00
const [ filtersToApply , setLocationFiltersToApply ] = useSessionStorage < IPos [ ] > (
` source-mangas-location- ${ locationKey } - ${ sourceId } -filters ` ,
currentFiltersToApply ? ? [ ] ,
) ;
2024-04-07 15:22:03 +02:00
const [ dialogFiltersToApply , setDialogFiltersToApply ] = useState < IPos [ ] > ( filtersToApply ) ;
2024-04-14 15:22:35 +02:00
const [ currentContentType , setCurrentContentType ] = useSessionStorage < SourceContentType | undefined > (
` source-mangas- ${ sourceId } -content-type ` ,
initialContentType ,
) ;
const [ contentType , setLocationContentType ] = useSessionStorage (
2024-04-07 15:22:03 +02:00
` source-mangas-location- ${ locationKey } - ${ sourceId } -content-type ` ,
2024-04-14 15:22:35 +02:00
query ? SourceContentType.SEARCH : currentContentType ! ,
2024-04-07 15:22:03 +02:00
) ;
2025-05-25 15:36:25 +02:00
const { key : persistedGridStateKey , deleteState : deletePersistedGridState } =
VirtuosoUtil . usePersistState ( MANGA_GRID_SNAPSHOT_KEY ) ;
2024-10-07 13:07:57 +02:00
const scrollToTop = useCallback ( ( ) = > {
2025-05-25 15:36:25 +02:00
deletePersistedGridState ( ) ;
2024-10-07 13:07:57 +02:00
window . scrollTo ( 0 , 0 ) ;
2025-05-25 15:36:25 +02:00
} , [ persistedGridStateKey ] ) ;
2024-10-07 13:07:57 +02:00
const currentQuery = useRef ( query ) ;
const currentAbortRequest = useRef < ( reason : any ) = > void > ( ( ) = > { } ) ;
const didSearchChange = currentQuery . current !== query ;
if ( didSearchChange && contentType === SourceContentType . SEARCH ) {
currentQuery . current = query ;
currentAbortRequest . current ( new Error ( ` SourceMangas( ${ sourceId } ): search string changed ` ) ) ;
scrollToTop ( ) ;
}
2024-04-14 15:18:51 +02:00
useEffect (
( ) = > ( ) = > {
setCurrentFiltersToApply ( undefined ) ;
2024-04-14 15:22:35 +02:00
setCurrentContentType ( undefined ) ;
2024-04-14 15:18:51 +02:00
} ,
[ sourceId ] ,
) ;
const setFiltersToApply = ( filters : IPos [ ] ) = > {
setCurrentFiltersToApply ( filters ) ;
setLocationFiltersToApply ( filters ) ;
2024-04-16 02:49:08 +02:00
scrollToTop ( ) ;
2024-04-14 15:18:51 +02:00
} ;
2024-04-14 15:22:35 +02:00
const setContentType = ( newContentType : SourceContentType ) = > {
setCurrentContentType ( newContentType ) ;
setLocationContentType ( newContentType ) ;
} ;
2025-05-17 19:53:56 +02:00
const gridKey = ` ${ contentType } ${ JSON . stringify ( filtersToApply ) } ${ query } ` ;
2024-10-06 16:15:01 +02:00
const [
loadPage ,
{ data , error , isLoading : loading , size : lastPageNum , abortRequest , filteredOutAllItemsOfFetchedPage } ,
] = useSourceManga ( sourceId , contentType , query , filtersToApply , 1 , hideLibraryEntries ) ;
2024-10-07 13:07:57 +02:00
currentAbortRequest . current = abortRequest ;
2024-03-23 23:44:46 +01:00
const isLoading = loading || filteredOutAllItemsOfFetchedPage ;
2024-06-02 15:24:29 +02:00
const mangas = data ? . fetchSourceManga ? . mangas ? ? [ ] ;
const hasNextPage = ! ! data ? . fetchSourceManga ? . hasNextPage ;
2024-07-03 23:15:50 +02:00
const { data : sourceData } = requestManager . useGetSource < GetSourceBrowseQuery , GetSourceBrowseQueryVariables > (
GET_SOURCE_BROWSE ,
sourceId ,
) ;
2023-09-12 02:36:11 +02:00
const source = sourceData ? . source ;
2024-05-08 04:01:23 +02:00
2023-09-24 21:32:23 +02:00
const filters = source ? . filters ? ? [ ] ;
2024-10-26 17:25:04 +02:00
const { savedSearches = { } } = useGetSourceMetadata ( source ? ? DEFAULT_SOURCE ) ;
2024-12-20 17:24:40 +01:00
const updateSourceMetadata = createUpdateSourceMetadata < 'savedSearches' > ( source ? ? { id : '-1' } , ( e ) = >
makeToast ( t ( 'global.error.label.failed_to_save_changes' ) , 'error' , getErrorMessage ( e ) ) ,
2024-05-08 04:01:23 +02:00
) ;
const selectSavedSearch = useCallback (
( savedSearch : string ) = > {
const { query : savedSearchQuery , filters : savedSearchFilters } = savedSearches [ savedSearch ] ;
if ( savedSearchFilters ) {
setDialogFiltersToApply ( savedSearchFilters ) ;
setFiltersToApply ( savedSearchFilters ) ;
}
2024-12-27 04:11:02 +01:00
if ( savedSearchQuery ) {
2025-07-23 01:52:41 +02:00
searchParams . set ( SearchParam . QUERY , savedSearchQuery ) ;
2024-12-27 04:11:02 +01:00
setSearchParams ( searchParams ) ;
}
2024-05-08 04:01:23 +02:00
} ,
[ savedSearches , locationState ] ,
) ;
const handleSavedSearchesUpdate = useCallback (
( savedSearch : string , updateType : 'create' | 'delete' ) = > {
if ( updateType === 'delete' ) {
const savedSearchesCopy = { . . . savedSearches } ;
delete savedSearchesCopy [ savedSearch ] ;
updateSourceMetadata ( 'savedSearches' , savedSearchesCopy ) ;
return ;
}
const updatedSavedSearches = {
. . . savedSearches ,
2024-12-20 19:27:22 +01:00
[ savedSearch ] : { query : query ? ? undefined , filters : dialogFiltersToApply } ,
2024-05-08 04:01:23 +02:00
} ;
updateSourceMetadata ( 'savedSearches' , updatedSavedSearches ) ;
} ,
2024-12-20 19:27:22 +01:00
[ savedSearches , query , dialogFiltersToApply ] ,
2024-05-08 04:01:23 +02:00
) ;
2022-03-04 01:25:10 +00:00
2023-07-04 21:53:25 +02:00
const message = ! isLoading ? t ( SOURCE_CONTENT_TYPE_TO_ERROR_MSG_KEY [ contentType ] ) : undefined ;
2025-04-21 17:00:13 +02:00
const isLocalSource = sourceId === Sources . LOCAL_SOURCE_ID ;
2023-05-28 12:16:06 +02:00
const messageExtra = isLocalSource ? (
< >
< span > { t ( 'source.local_source.label.checkout' ) } < / span >
2024-05-04 23:54:33 +02:00
< Link href = "https://github.com/Suwayomi/Suwayomi-Server/wiki/Local-Source" target = "_blank" rel = "noreferrer" >
2023-05-28 12:16:06 +02:00
{ t ( 'source.local_source.label.guide' ) }
< / Link >
< / >
) : undefined ;
2022-03-06 21:02:06 +00:00
2023-06-17 16:29:55 +02:00
const updateContentType = useCallback (
2024-04-07 15:22:03 +02:00
( newContentType : SourceContentType , newSearch? : string | null ) = > {
setContentType ( newContentType ) ;
2024-04-16 02:49:08 +02:00
scrollToTop ( ) ;
2024-04-07 15:22:03 +02:00
if ( query && ! newSearch ) {
2024-01-11 02:57:56 +01:00
navigate (
{
2024-04-07 15:22:03 +02:00
pathname : '' ,
} ,
{
state : { . . . locationState , contentType : newContentType } ,
2024-01-11 02:57:56 +01:00
} ,
) ;
2023-06-17 16:29:55 +02:00
}
} ,
2024-04-16 02:49:08 +02:00
[ setContentType , query , scrollToTop ] ,
2023-09-24 22:34:37 +02:00
) ;
2024-04-07 15:22:03 +02:00
const setSearchContentType = ! ! query && contentType !== SourceContentType . SEARCH ;
2023-05-28 12:16:06 +02:00
if ( setSearchContentType ) {
2024-04-07 15:22:03 +02:00
updateContentType ( SourceContentType . SEARCH , query ) ;
2023-05-28 12:16:06 +02:00
}
2021-09-13 19:18:21 +04:30
2023-06-17 16:50:17 +02:00
const loadMore = useCallback ( ( ) = > {
if ( ! hasNextPage ) {
2023-05-18 13:25:19 +02:00
return ;
}
2023-09-12 02:36:11 +02:00
loadPage ( lastPageNum + 1 ) ;
} , [ lastPageNum , hasNextPage , contentType ] ) ;
2022-03-04 01:25:10 +00:00
2024-05-08 04:01:23 +02:00
const resetFilters = ( ) = > {
2023-05-28 12:16:06 +02:00
setDialogFiltersToApply ( [ ] ) ;
setFiltersToApply ( [ ] ) ;
2024-05-08 04:01:23 +02:00
} ;
2023-05-28 12:16:06 +02:00
2025-05-17 23:37:00 +02:00
useEffect ( ( ) = > {
updateMetadataServerSettings ( 'lastUsedSourceId' , sourceId ) . catch (
defaultPromiseErrorHandler ( 'SourceMangas::setLastUsedSourceId' ) ,
) ;
} , [ sourceId ] ) ;
2024-03-23 23:44:46 +01:00
useEffect ( ( ) = > {
if ( filteredOutAllItemsOfFetchedPage && hasNextPage && ! loading ) {
loadPage ( lastPageNum + 1 ) ;
}
} , [ filteredOutAllItemsOfFetchedPage , loading ] ) ;
2023-11-11 23:43:22 +01:00
useEffect ( ( ) = > {
if ( ! clearCache ) {
return ;
}
const requiresClear = SPECIAL_ED_SOURCES . REVALIDATION . includes ( sourceId ) ;
if ( ! requiresClear ) {
return ;
}
requestManager . clearBrowseCacheFor ( sourceId ) ;
} , [ clearCache ] ) ;
2025-05-03 13:37:20 +02:00
useAppTitleAndAction (
source ? . displayName ? ? t ( 'source.title_one' ) ,
2025-05-03 13:24:55 +02:00
< >
< AppbarSearch / >
< SourceGridLayout / >
{ source ? . isConfigurable && (
< CustomTooltip title = { t ( 'settings.title' ) } >
< IconButton
onClick = { ( ) = > navigate ( AppRoutes . sources . childRoutes . configure . path ( sourceId ) ) }
aria - label = "display more actions"
edge = "end"
color = "inherit"
>
< SettingsIcon / >
< / IconButton >
< / CustomTooltip >
) }
< / > ,
2025-05-03 13:37:20 +02:00
[ source ] ,
2025-05-03 13:24:55 +02:00
) ;
2021-09-18 00:05:00 +04:30
2024-12-20 19:15:18 +01:00
const EmptyViewComponent = mangas . length ? EmptyView : EmptyViewAbsoluteCentered ;
2021-01-22 21:11:00 +03:30
return (
2024-09-14 03:01:07 +02:00
< StyledGridWrapper >
2024-12-21 23:21:55 +01:00
< ContentTypeMenu sx = { { top : ` ${ appBarHeight } px ` } } >
2023-05-28 12:16:06 +02:00
< ContentTypeButton
variant = { contentType === SourceContentType . POPULAR ? 'contained' : 'outlined' }
startIcon = { < FavoriteIcon / > }
onClick = { ( ) = > updateContentType ( SourceContentType . POPULAR ) }
>
{ t ( 'global.button.popular' ) }
< / ContentTypeButton >
2023-11-05 17:22:16 +01:00
{ source ? . supportsLatest === undefined || source . supportsLatest ? (
< ContentTypeButton
disabled = { ! source ? . supportsLatest }
variant = { contentType === SourceContentType . LATEST ? 'contained' : 'outlined' }
startIcon = { < NewReleasesIcon / > }
onClick = { ( ) = > updateContentType ( SourceContentType . LATEST ) }
>
{ t ( 'global.button.latest' ) }
< / ContentTypeButton >
) : null }
2023-05-28 12:16:06 +02:00
< ContentTypeButton
2024-01-11 02:57:56 +01:00
variant = { contentType === SourceContentType . SEARCH ? 'contained' : 'outlined' }
2023-05-28 12:16:06 +02:00
startIcon = { < FilterListIcon / > }
2024-04-07 15:22:03 +02:00
onClick = { ( ) = > updateContentType ( SourceContentType . SEARCH , query ) }
2023-05-28 12:16:06 +02:00
>
{ t ( 'global.button.filter' ) }
< / ContentTypeButton >
< / ContentTypeMenu >
2024-10-06 16:15:01 +02:00
{ ( isLoading || ! error || ( ! ! error && ! ! mangas . length ) ) && (
< BaseMangaGrid
2025-05-17 19:53:56 +02:00
// the key needs to include filters and query to force a re-render of the virtuoso grid to prevent https://github.com/petyosi/react-virtuoso/issues/1242
key = { gridKey }
2024-10-06 16:15:01 +02:00
gridWrapperProps = { { sx : { px : 1 , pb : 1 } } }
mangas = { mangas }
hasNextPage = { hasNextPage }
loadMore = { loadMore }
message = { message }
messageExtra = { messageExtra }
isLoading = { isLoading }
gridLayout = { sourceGridLayout }
mode = "source"
inLibraryIndicator
/ >
) }
2024-12-20 19:15:18 +01:00
{ error && (
< EmptyViewComponent
2024-10-06 16:15:01 +02:00
message = { t ( 'global.error.label.failed_to_load_data' ) }
2024-12-20 19:15:18 +01:00
messageExtra = { getErrorMessage ( error ) }
2024-10-06 16:15:01 +02:00
retry = { ( ) = > loadPage ( lastPageNum ) . catch ( defaultPromiseErrorHandler ( 'SourceMangas::refetch' ) ) }
/ >
) }
2024-01-11 02:57:56 +01:00
{ contentType === SourceContentType . SEARCH && (
2022-03-04 01:25:10 +00:00
< SourceOptions
2024-05-08 04:01:23 +02:00
savedSearches = { savedSearches }
selectSavedSearch = { selectSavedSearch }
updateSavedSearches = { handleSavedSearchesUpdate }
2023-05-28 12:16:06 +02:00
sourceFilter = { filters }
updateFilterValue = { setDialogFiltersToApply }
setTriggerUpdate = { ( ) = > {
setFiltersToApply ( dialogFiltersToApply ) ;
} }
resetFilterValue = { resetFilters }
update = { dialogFiltersToApply }
2022-03-04 01:25:10 +00:00
/ >
) }
2023-05-28 12:16:06 +02:00
< / StyledGridWrapper >
2021-01-22 21:11:00 +03:30
) ;
2021-01-19 14:47:07 +03:30
}