@@ -32,9 +32,9 @@ import type {
|
||||
|
||||
export type MangaCardMode = 'default' | 'source' | 'migrate.select.bulk' | 'migrate.select.single' | 'duplicate';
|
||||
|
||||
type MangaCardBaseProps = Pick<MangaTypeGql, 'id' | 'title' | 'sourceId'> &
|
||||
type MangaCardBaseProps = Pick<MangaTypeGql, 'id' | 'title' | 'sourceId' | 'inLibrary'> &
|
||||
Omit<SingleModeProps['manga'], 'downloadCount' | 'unreadCount' | 'chapters'> &
|
||||
Partial<Pick<MangaTypeGql, 'inLibrary' | 'downloadCount' | 'unreadCount'>> & {
|
||||
Partial<Pick<MangaTypeGql, 'downloadCount' | 'unreadCount'>> & {
|
||||
firstUnreadChapter?:
|
||||
| (ChapterIdInfo & ChapterSourceOrderInfo & ChapterReadInfo & ChapterNumberInfo & ChapterNameInfo)
|
||||
| null;
|
||||
|
||||
@@ -36,6 +36,7 @@ import type {
|
||||
MangaAction,
|
||||
MangaDownloadInfo,
|
||||
MangaIdInfo,
|
||||
MangaInLibraryInfo,
|
||||
MangaSourceIdInfo,
|
||||
MangaTitleInfo,
|
||||
MangaUnreadInfo,
|
||||
@@ -47,7 +48,12 @@ import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts';
|
||||
type BaseProps = { onClose: () => void; setHideMenu: (hide: boolean) => void };
|
||||
|
||||
export type SingleModeProps = {
|
||||
manga: MangaIdInfo & MangaTitleInfo & MangaSourceIdInfo & MangaDownloadInfo & MangaUnreadInfo;
|
||||
manga: MangaIdInfo &
|
||||
MangaTitleInfo &
|
||||
MangaSourceIdInfo &
|
||||
MangaDownloadInfo &
|
||||
MangaUnreadInfo &
|
||||
Pick<MangaInLibraryInfo, 'inLibrary'>;
|
||||
handleSelection?: SelectableCollectionReturnType<MangaIdInfo['id']>['handleSelection'];
|
||||
};
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import type {
|
||||
MangaArtistInfo,
|
||||
MangaAuthorInfo,
|
||||
MangaIdInfo,
|
||||
MangaInLibraryInfo,
|
||||
MangaSourceIdInfo,
|
||||
MangaThumbnailInfo,
|
||||
MangaTitleInfo,
|
||||
@@ -102,7 +103,14 @@ export enum MigrationEntryStatus {
|
||||
}
|
||||
|
||||
export interface MigrationMatch
|
||||
extends MangaIdInfo, MangaTitleInfo, MangaThumbnailInfo, MangaSourceIdInfo, MangaArtistInfo, MangaAuthorInfo {
|
||||
extends
|
||||
MangaIdInfo,
|
||||
MangaTitleInfo,
|
||||
MangaThumbnailInfo,
|
||||
MangaSourceIdInfo,
|
||||
MangaArtistInfo,
|
||||
MangaAuthorInfo,
|
||||
Pick<MangaInLibraryInfo, 'inLibrary'> {
|
||||
sourceTitle: SourceDisplayNameInfo['displayName'] | undefined;
|
||||
latestChapterNumber: ChapterNumberInfo['chapterNumber'] | undefined;
|
||||
missingChapters: number | undefined;
|
||||
|
||||
@@ -249,6 +249,7 @@ export class MigrationManager {
|
||||
sourceTitle: cachedEntry?.source?.displayName ?? searchMatch.sourceTitle,
|
||||
latestChapterNumber: cachedEntry?.highestNumberedChapter?.chapterNumber ?? searchMatch.latestChapterNumber,
|
||||
missingChapters: searchMatch.missingChapters,
|
||||
inLibrary: cachedEntry?.inLibrary ?? searchMatch.inLibrary,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -955,6 +956,7 @@ export class MigrationManager {
|
||||
sourceId: manga.sourceId,
|
||||
sourceTitle: manga.source?.displayName,
|
||||
missingChapters: chapters ? Chapters.getMissingCount(chapters) : undefined,
|
||||
inLibrary: manga.inLibrary,
|
||||
}));
|
||||
|
||||
draftEntry.destSourceIdToSearchState[destSourceId] = true;
|
||||
|
||||
@@ -18,8 +18,6 @@ import { MigrationManager } from '@/features/migration/MigrationManager.ts';
|
||||
import { extractGraphqlExceptionInfo } from '@/lib/HelperFunctions.ts';
|
||||
import { Confirmation } from '@/base/AppAwaitableComponent.ts';
|
||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||
import { ListCardAvatar } from '@/base/components/lists/cards/ListCardAvatar.tsx';
|
||||
import { Mangas } from '@/features/manga/services/Mangas.ts';
|
||||
import { TypographyMaxLines } from '@/base/components/texts/TypographyMaxLines.tsx';
|
||||
import { MigrationEntryMetadataText } from '@/features/migration/components/migration-entry/MigrationEntryMetadataText.tsx';
|
||||
import Typography from '@mui/material/Typography';
|
||||
@@ -36,6 +34,7 @@ import Link from '@mui/material/Link';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { applyStyles } from '@/base/utils/ApplyStyles.ts';
|
||||
import { MigrationEntryThumbnail } from '@/features/migration/components/migration-entry/MigrationEntryThumbnail.tsx';
|
||||
|
||||
const EntryStatus = ({
|
||||
sourceMangaId,
|
||||
@@ -153,21 +152,7 @@ const EntryData = (entry: MigrationMatch) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Link component={RouterLink} to={AppRoutes.manga.path(id)}>
|
||||
<ListCardAvatar
|
||||
iconUrl={Mangas.getThumbnailUrl(entry)}
|
||||
alt={title}
|
||||
slots={{
|
||||
avatarProps: {
|
||||
sx: {
|
||||
width: 'unset',
|
||||
height: 112,
|
||||
aspectRatio: '3 / 4',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Link>
|
||||
<MigrationEntryThumbnail entry={entry} height={112} />
|
||||
<Stack sx={{ minWidth: 0, flex: 1 }}>
|
||||
<Typography
|
||||
variant="overline"
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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 FavoriteIcon from '@mui/icons-material/Favorite';
|
||||
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||
import { ListCardAvatar } from '@/base/components/lists/cards/ListCardAvatar.tsx';
|
||||
import { Mangas } from '@/features/manga/services/Mangas.ts';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Link from '@mui/material/Link';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import type { MigrationMatch } from '@/features/migration/Migration.types.ts';
|
||||
import type { Theme } from '@mui/material/styles';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
|
||||
|
||||
export const MigrationEntryThumbnail = ({
|
||||
entry,
|
||||
height,
|
||||
}: {
|
||||
entry: MigrationMatch;
|
||||
height: ReturnType<Theme['applyStyles']>['height'];
|
||||
}) => {
|
||||
const { id, title } = entry;
|
||||
|
||||
const { t } = useLingui();
|
||||
|
||||
return (
|
||||
<Stack sx={{ position: 'relative' }}>
|
||||
<Link component={RouterLink} to={AppRoutes.manga.path(id)} onClick={(e) => e.stopPropagation()}>
|
||||
<ListCardAvatar
|
||||
iconUrl={Mangas.getThumbnailUrl(entry)}
|
||||
alt={title}
|
||||
slots={{
|
||||
avatarProps: {
|
||||
sx: {
|
||||
width: 'unset',
|
||||
height,
|
||||
aspectRatio: '3 / 4',
|
||||
filter: entry.inLibrary ? 'brightness(0.4)' : undefined,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Link>
|
||||
{entry.inLibrary && (
|
||||
<CustomTooltip title={t`In Library`}>
|
||||
<Stack
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 2.5,
|
||||
left: 2.5,
|
||||
p: 0.5,
|
||||
backgroundColor: 'background.paper',
|
||||
borderRadius: 1,
|
||||
}}
|
||||
>
|
||||
<FavoriteIcon color="primary" sx={{ fontSize: 15 }} />
|
||||
</Stack>
|
||||
</CustomTooltip>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -12,8 +12,6 @@ import { MigrationEntryCard } from '@/features/migration/components/migration-en
|
||||
import { MigrationManager } from '@/features/migration/MigrationManager.ts';
|
||||
import { MigrationEntryCardContent } from '@/features/migration/components/migration-entry/MigrationEntryCardContent.tsx';
|
||||
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||
import { ListCardAvatar } from '@/base/components/lists/cards/ListCardAvatar.tsx';
|
||||
import { Mangas } from '@/features/manga/services/Mangas.ts';
|
||||
import { TypographyMaxLines } from '@/base/components/texts/TypographyMaxLines.tsx';
|
||||
import { MigrationEntryMetadataText } from '@/features/migration/components/migration-entry/MigrationEntryMetadataText.tsx';
|
||||
import { MUIUtil } from '@/lib/mui/MUI.util.ts';
|
||||
@@ -25,6 +23,7 @@ import CardActionArea from '@mui/material/CardActionArea';
|
||||
import Link from '@mui/material/Link';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { memo } from 'react';
|
||||
import { MigrationEntryThumbnail } from '@/features/migration/components/migration-entry/MigrationEntryThumbnail.tsx';
|
||||
|
||||
export const MigrationMatchedEntry = memo(
|
||||
({ sourceMangaId, entry }: { sourceMangaId: MangaIdInfo['id']; entry: MigrationMatch }) => {
|
||||
@@ -36,25 +35,7 @@ export const MigrationMatchedEntry = memo(
|
||||
<MigrationEntryCardContent>
|
||||
{(() => (
|
||||
<>
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={AppRoutes.manga.path(entry.id)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<ListCardAvatar
|
||||
iconUrl={Mangas.getThumbnailUrl(entry)}
|
||||
alt={entry.title}
|
||||
slots={{
|
||||
avatarProps: {
|
||||
sx: {
|
||||
width: 'unset',
|
||||
height: 80,
|
||||
aspectRatio: '3 / 4',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Link>
|
||||
<MigrationEntryThumbnail entry={entry} height={80} />
|
||||
<Stack sx={{ minWidth: 0, flex: 1 }}>
|
||||
<Typography variant="overline" color="textSecondary">
|
||||
{entry.sourceTitle}
|
||||
|
||||
Reference in New Issue
Block a user