Optionally trigger manga details search in source

In case the manga was opened while browsing the source, the searches should be triggered for in the search instead of the library
This commit is contained in:
schroda
2025-05-30 18:46:57 +02:00
parent 70a984e2cb
commit 18da4c6b64
7 changed files with 111 additions and 34 deletions

View File

@@ -15,7 +15,13 @@ import { useGetCategoryMetadata } from '@/modules/category/services/CategoryMeta
import { NullAndUndefined } from '@/Base.types.ts';
import { LibraryOptions, LibrarySortMode } from '@/modules/library/Library.types.ts';
import { CategoryIdInfo, CategoryMetadataInfo } from '@/modules/category/Category.types.ts';
import { MangaChapterCountInfo, MangaIdInfo } from '@/modules/manga/Manga.types.ts';
import {
MangaChapterCountInfo,
MangaDownloadInfo,
MangaIdInfo,
MangaStatusInfo,
MangaUnreadInfo,
} from '@/modules/manga/Manga.types.ts';
import { SourceDisplayNameInfo } from '@/modules/source/Source.types.ts';
const triStateFilter = (
@@ -96,8 +102,7 @@ const trackerFilter = (trackFilters: LibraryOptions['hasTrackerBinding'], manga:
})
.every(Boolean);
type TMangaStatusFilter = Pick<MangaType, 'status'>;
const statusFilter = (statusFilters: LibraryOptions['hasStatus'], manga: TMangaStatusFilter): boolean =>
const statusFilter = (statusFilters: LibraryOptions['hasStatus'], manga: MangaStatusInfo): boolean =>
Object.entries(statusFilters)
.map(([status, statusFilterState]) => triStateFilterBoolean(statusFilterState, status === manga.status))
.every(Boolean);
@@ -112,10 +117,12 @@ type TMangaFilterOptions = Pick<
| 'hasTrackerBinding'
| 'hasStatus'
>;
type TMangaFilter = Pick<MangaType, 'downloadCount' | 'unreadCount' | 'bookmarkCount' | 'hasDuplicateChapters'> &
type TMangaFilter = Pick<MangaType, 'bookmarkCount' | 'hasDuplicateChapters'> &
TMangaTrackerFilter &
TMangaStatusFilter &
MangaChapterCountInfo;
MangaStatusInfo &
MangaChapterCountInfo &
MangaDownloadInfo &
MangaUnreadInfo;
const filterManga = (
manga: TMangaFilter,
{

View File

@@ -33,6 +33,7 @@ type MangaCardBaseProps = Pick<MangaTypeGql, 'id' | 'title' | 'sourceId'> &
export type MangaIdInfo = Pick<MangaTypeGql, 'id'>;
export type MangaChapterCountInfo = { chapters: Pick<MangaTypeGql['chapters'], 'totalCount'> };
export type MangaInLibraryInfo = Pick<MangaTypeGql, 'inLibrary'>;
export type MangaDownloadInfo = Pick<MangaTypeGql, 'downloadCount'> & MangaChapterCountInfo;
export type MangaUnreadInfo = Pick<MangaTypeGql, 'unreadCount'> & MangaChapterCountInfo;
export type MangaThumbnailInfo = Pick<MangaTypeGql, 'thumbnailUrl' | 'thumbnailUrlLastFetched'>;
@@ -40,10 +41,14 @@ export type MangaTrackRecordInfo = MangaIdInfo & {
trackRecords: { nodes: Pick<TrackRecordType, 'id' | 'trackerId'>[] };
};
export type MangaGenreInfo = Pick<MangaTypeGql, 'genre'>;
export type MangaSourceIdInfo = Pick<MangaTypeGql, 'sourceId'>;
export type MangaSourceNameInfo = { source?: Maybe<Pick<SourceType, 'name'>> };
export type MangaSourceLngInfo = { source?: Maybe<Pick<SourceType, 'lang'>> };
export type MangaArtistInfo = Pick<MangaTypeGql, 'artist'>;
export type MangaAuthorInfo = Pick<MangaTypeGql, 'author'>;
export type MangaTitleInfo = Pick<MangaTypeGql, 'title'>;
export type MangaDescriptionInfo = Pick<MangaTypeGql, 'description'>;
export type MangaStatusInfo = Pick<MangaTypeGql, 'status'>;
export type MigrateMode = 'copy' | 'migrate';
@@ -92,3 +97,8 @@ export enum MangaType {
MANHWA,
MANHUA,
}
export interface MangaLocationState {
mangaTitle: string;
mode: MangaCardMode | undefined;
}

View File

@@ -44,12 +44,26 @@ import { useLocalStorage } from '@/modules/core/hooks/useStorage.tsx';
import { useResizeObserver } from '@/modules/core/hooks/useResizeObserver.tsx';
import { useMetadataServerSettings } from '@/modules/settings/services/ServerSettingsMetadata.ts';
import { MANGA_COVER_ASPECT_RATIO, MANGA_STATUS_TO_TRANSLATION } from '@/modules/manga/Manga.constants.ts';
import { MangaThumbnailInfo, MangaTrackRecordInfo } from '@/modules/manga/Manga.types.ts';
import {
MangaArtistInfo,
MangaAuthorInfo,
MangaDescriptionInfo,
MangaGenreInfo,
MangaIdInfo,
MangaInLibraryInfo,
MangaLocationState,
MangaSourceIdInfo,
MangaStatusInfo,
MangaThumbnailInfo,
MangaTitleInfo,
MangaTrackRecordInfo,
} from '@/modules/manga/Manga.types.ts';
import { TAppThemeContext, useAppThemeContext } from '@/modules/theme/contexts/AppThemeContext.tsx';
import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts';
import { CustomButtonIcon } from '@/modules/core/components/buttons/CustomButtonIcon.tsx';
import { Sources } from '@/modules/source/services/Sources.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
import { SourceIdInfo } from '@/modules/source/Source.types.ts';
const DetailsWrapper = styled('div')(({ theme }) => ({
display: 'flex',
@@ -163,19 +177,40 @@ function getSourceName(source?: Pick<SourceType, 'id' | 'displayName'> | null):
return source.displayName ?? source.id;
}
const LibrarySearchLink = ({ query, children }: { query: string; children?: ReactNode }) => (
<Link
component={RouterLink}
to={AppRoutes.library.path(undefined, query)}
sx={{ textDecoration: 'none', color: 'inherit' }}
>
const SearchLink = ({
query,
sourceId,
mode,
children,
}: {
query: string;
sourceId: SourceIdInfo['id'] | undefined;
mode: MangaLocationState['mode'];
children?: ReactNode;
}) => {
const link = (() => {
const isSourceMode = mode === 'source' && sourceId !== undefined;
if (isSourceMode) {
return AppRoutes.sources.childRoutes.browse.path(sourceId, query);
}
return AppRoutes.library.path(undefined, query);
})();
return (
<Link component={RouterLink} to={link} sx={{ textDecoration: 'none', color: 'inherit' }}>
{children ?? query}
</Link>
);
};
const valuesToJoinedLibrarySearchLinks = (values: string[] | undefined) =>
const valuesToJoinedSearchLinks = (
values: string[] | undefined,
sourceId: SourceIdInfo['id'] | undefined,
mode: MangaLocationState['mode'],
) =>
values
?.map((value) => <LibrarySearchLink query={value} />)
?.map((value) => <SearchLink key={value} query={value} sourceId={sourceId} mode={mode} />)
.reduce((acc, valueLink) => (
<>
{acc}, {valueLink}
@@ -310,9 +345,11 @@ const Thumbnail = ({
const OPEN_CLOSE_BUTTON_HEIGHT = '35px';
const DESCRIPTION_COLLAPSED_SIZE = 75;
const DescriptionGenre = ({
manga: { description, genre: mangaGenres },
manga: { description, genre: mangaGenres, sourceId },
mode,
}: {
manga: Pick<MangaType, 'description' | 'genre'>;
manga: MangaDescriptionInfo & MangaGenreInfo & MangaSourceIdInfo;
mode: MangaLocationState['mode'];
}) => {
const [descriptionElement, setDescriptionElement] = useState<HTMLSpanElement | null>(null);
const [descriptionHeight, setDescriptionHeight] = useState<number>();
@@ -374,9 +411,9 @@ const DescriptionGenre = ({
}}
>
{genres.map((genre) => (
<LibrarySearchLink key={genre} query={genre}>
<SearchLink key={genre} query={genre} sourceId={sourceId} mode={mode}>
<Chip label={genre} variant="outlined" onClick={() => {}} />
</LibrarySearchLink>
</SearchLink>
))}
</Stack>
</>
@@ -385,15 +422,23 @@ const DescriptionGenre = ({
export const MangaDetails = ({
manga,
mode,
}: {
manga: Pick<
MangaType,
'id' | 'title' | 'author' | 'artist' | 'status' | 'inLibrary' | 'realUrl' | 'description' | 'genre'
> &
manga: Pick<MangaType, 'realUrl'> &
MangaIdInfo &
MangaTitleInfo &
MangaStatusInfo &
MangaInLibraryInfo &
MangaAuthorInfo &
MangaArtistInfo &
MangaDescriptionInfo &
MangaGenreInfo &
MangaThumbnailInfo &
MangaSourceIdInfo &
MangaTrackRecordInfo & {
source?: Pick<SourceType, 'id' | 'displayName'> | null;
};
mode: MangaLocationState['mode'];
}) => {
const { t } = useTranslation();
@@ -438,13 +483,13 @@ export const MangaDetails = ({
{manga.author && (
<Metadata
title={t('manga.label.author')}
value={valuesToJoinedLibrarySearchLinks(Mangas.getAuthors(manga))}
value={valuesToJoinedSearchLinks(Mangas.getAuthors(manga), manga.source?.id, mode)}
/>
)}
{manga.artist && (
<Metadata
title={t('manga.label.artist')}
value={valuesToJoinedLibrarySearchLinks(Mangas.getArtists(manga))}
value={valuesToJoinedSearchLinks(Mangas.getArtists(manga), manga.source?.id, mode)}
/>
)}
<Metadata
@@ -467,7 +512,7 @@ export const MangaDetails = ({
<OpenSourceButton url={manga.realUrl} />
</MangaButtonsContainer>
</TopContentWrapper>
<DescriptionGenre manga={manga} />
<DescriptionGenre manga={manga} mode={mode} />
</DetailsWrapper>
{CategorySelectComponent}
</>

View File

@@ -65,7 +65,7 @@ export const MangaGridCard = memo(
{...longPressBind(() => popupState.open(optionButtonRef.current))}
onClick={handleClick}
to={mangaLinkTo}
state={{ mangaTitle: title }}
state={Mangas.createLocationState(manga, mode)}
sx={{ textDecoration: 'none', touchCallout: 'none' }}
>
<Box

View File

@@ -44,7 +44,7 @@ export const MangaListCard = memo(
<CardActionArea
component={RouterLink}
to={mangaLinkTo}
state={{ mangaTitle: title }}
state={Mangas.createLocationState(manga, mode)}
onClick={handleClick}
{...longPressBind(() => popupState.open(optionButtonRef.current))}
sx={{

View File

@@ -13,7 +13,7 @@ import Stack from '@mui/material/Stack';
import Box from '@mui/material/Box';
import React, { useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import { useLocation, useParams } from 'react-router-dom';
import { isNetworkRequestInFlight } from '@apollo/client/core/networkStatus';
import { CustomTooltip } from '@/modules/core/components/CustomTooltip.tsx';
import { requestManager } from '@/lib/requests/RequestManager.ts';
@@ -27,11 +27,13 @@ import { GetMangaScreenQuery } from '@/lib/graphql/generated/graphql.ts';
import { GET_MANGA_SCREEN } from '@/lib/graphql/queries/MangaQuery.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
import { useAppTitleAndAction } from '@/modules/navigation-bar/hooks/useAppTitleAndAction.ts';
import { MangaLocationState } from '@/modules/manga/Manga.types.ts';
export const Manga: React.FC = () => {
const { t } = useTranslation();
const { id } = useParams<{ id: string }>();
const { mode } = useLocation<MangaLocationState>().state ?? {};
const autofetchedRef = useRef(false);
const {
@@ -103,7 +105,7 @@ export const Manga: React.FC = () => {
<Box sx={{ display: { md: 'flex' }, overflow: 'hidden' }}>
{isLoading && <LoadingPlaceholder />}
{manga && <MangaDetails manga={manga} />}
{manga && <MangaDetails manga={manga} mode={mode} />}
{manga && <ChapterList manga={manga} isRefreshing={refreshing} />}
</Box>
);

View File

@@ -29,12 +29,15 @@ import {
MangaAction,
MangaArtistInfo,
MangaAuthorInfo,
MangaCardMode,
MangaDownloadInfo,
MangaGenreInfo,
MangaIdInfo,
MangaLocationState,
MangaSourceLngInfo,
MangaSourceNameInfo,
MangaThumbnailInfo,
MangaTitleInfo,
MangaType,
MangaUnreadInfo,
MigrateMode,
@@ -604,4 +607,14 @@ export class Mangas {
static getAuthors<Manga extends MangaAuthorInfo>(manga: Manga): string[] | undefined {
return manga.author?.split(ARTIST_AUTHOR_SEPARATOR_REGEX);
}
static createLocationState<Manga extends MangaTitleInfo>(
manga: Manga,
mode: MangaCardMode | undefined,
): MangaLocationState {
return {
mangaTitle: manga.title,
mode,
};
}
}