@@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|||||||
- (**Migration**) Add a search option to ignore matches with missing chapters
|
- (**Migration**) Add a search option to ignore matches with missing chapters
|
||||||
- (**Migration**) Add "local source" as a possible destination source
|
- (**Migration**) Add "local source" as a possible destination source
|
||||||
- (**Migration**) Add option to abort entries that are searching or are waiting to get migrated
|
- (**Migration**) Add option to abort entries that are searching or are waiting to get migrated
|
||||||
|
- (**Migration**) Add "in library" indicator
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ import type {
|
|||||||
|
|
||||||
export type MangaCardMode = 'default' | 'source' | 'migrate.select.bulk' | 'migrate.select.single' | 'duplicate';
|
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'> &
|
Omit<SingleModeProps['manga'], 'downloadCount' | 'unreadCount' | 'chapters'> &
|
||||||
Partial<Pick<MangaTypeGql, 'inLibrary' | 'downloadCount' | 'unreadCount'>> & {
|
Partial<Pick<MangaTypeGql, 'downloadCount' | 'unreadCount'>> & {
|
||||||
firstUnreadChapter?:
|
firstUnreadChapter?:
|
||||||
| (ChapterIdInfo & ChapterSourceOrderInfo & ChapterReadInfo & ChapterNumberInfo & ChapterNameInfo)
|
| (ChapterIdInfo & ChapterSourceOrderInfo & ChapterReadInfo & ChapterNumberInfo & ChapterNameInfo)
|
||||||
| null;
|
| null;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import type {
|
|||||||
MangaAction,
|
MangaAction,
|
||||||
MangaDownloadInfo,
|
MangaDownloadInfo,
|
||||||
MangaIdInfo,
|
MangaIdInfo,
|
||||||
|
MangaInLibraryInfo,
|
||||||
MangaSourceIdInfo,
|
MangaSourceIdInfo,
|
||||||
MangaTitleInfo,
|
MangaTitleInfo,
|
||||||
MangaUnreadInfo,
|
MangaUnreadInfo,
|
||||||
@@ -47,7 +48,12 @@ import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts';
|
|||||||
type BaseProps = { onClose: () => void; setHideMenu: (hide: boolean) => void };
|
type BaseProps = { onClose: () => void; setHideMenu: (hide: boolean) => void };
|
||||||
|
|
||||||
export type SingleModeProps = {
|
export type SingleModeProps = {
|
||||||
manga: MangaIdInfo & MangaTitleInfo & MangaSourceIdInfo & MangaDownloadInfo & MangaUnreadInfo;
|
manga: MangaIdInfo &
|
||||||
|
MangaTitleInfo &
|
||||||
|
MangaSourceIdInfo &
|
||||||
|
MangaDownloadInfo &
|
||||||
|
MangaUnreadInfo &
|
||||||
|
Pick<MangaInLibraryInfo, 'inLibrary'>;
|
||||||
handleSelection?: SelectableCollectionReturnType<MangaIdInfo['id']>['handleSelection'];
|
handleSelection?: SelectableCollectionReturnType<MangaIdInfo['id']>['handleSelection'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import type {
|
|||||||
MangaArtistInfo,
|
MangaArtistInfo,
|
||||||
MangaAuthorInfo,
|
MangaAuthorInfo,
|
||||||
MangaIdInfo,
|
MangaIdInfo,
|
||||||
|
MangaInLibraryInfo,
|
||||||
MangaSourceIdInfo,
|
MangaSourceIdInfo,
|
||||||
MangaThumbnailInfo,
|
MangaThumbnailInfo,
|
||||||
MangaTitleInfo,
|
MangaTitleInfo,
|
||||||
@@ -102,7 +103,14 @@ export enum MigrationEntryStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface MigrationMatch
|
export interface MigrationMatch
|
||||||
extends MangaIdInfo, MangaTitleInfo, MangaThumbnailInfo, MangaSourceIdInfo, MangaArtistInfo, MangaAuthorInfo {
|
extends
|
||||||
|
MangaIdInfo,
|
||||||
|
MangaTitleInfo,
|
||||||
|
MangaThumbnailInfo,
|
||||||
|
MangaSourceIdInfo,
|
||||||
|
MangaArtistInfo,
|
||||||
|
MangaAuthorInfo,
|
||||||
|
Pick<MangaInLibraryInfo, 'inLibrary'> {
|
||||||
sourceTitle: SourceDisplayNameInfo['displayName'] | undefined;
|
sourceTitle: SourceDisplayNameInfo['displayName'] | undefined;
|
||||||
latestChapterNumber: ChapterNumberInfo['chapterNumber'] | undefined;
|
latestChapterNumber: ChapterNumberInfo['chapterNumber'] | undefined;
|
||||||
missingChapters: number | undefined;
|
missingChapters: number | undefined;
|
||||||
|
|||||||
@@ -249,6 +249,7 @@ export class MigrationManager {
|
|||||||
sourceTitle: cachedEntry?.source?.displayName ?? searchMatch.sourceTitle,
|
sourceTitle: cachedEntry?.source?.displayName ?? searchMatch.sourceTitle,
|
||||||
latestChapterNumber: cachedEntry?.highestNumberedChapter?.chapterNumber ?? searchMatch.latestChapterNumber,
|
latestChapterNumber: cachedEntry?.highestNumberedChapter?.chapterNumber ?? searchMatch.latestChapterNumber,
|
||||||
missingChapters: searchMatch.missingChapters,
|
missingChapters: searchMatch.missingChapters,
|
||||||
|
inLibrary: cachedEntry?.inLibrary ?? searchMatch.inLibrary,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -955,6 +956,7 @@ export class MigrationManager {
|
|||||||
sourceId: manga.sourceId,
|
sourceId: manga.sourceId,
|
||||||
sourceTitle: manga.source?.displayName,
|
sourceTitle: manga.source?.displayName,
|
||||||
missingChapters: chapters ? Chapters.getMissingCount(chapters) : undefined,
|
missingChapters: chapters ? Chapters.getMissingCount(chapters) : undefined,
|
||||||
|
inLibrary: manga.inLibrary,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
draftEntry.destSourceIdToSearchState[destSourceId] = true;
|
draftEntry.destSourceIdToSearchState[destSourceId] = true;
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ import { MigrationManager } from '@/features/migration/MigrationManager.ts';
|
|||||||
import { extractGraphqlExceptionInfo } from '@/lib/HelperFunctions.ts';
|
import { extractGraphqlExceptionInfo } from '@/lib/HelperFunctions.ts';
|
||||||
import { Confirmation } from '@/base/AppAwaitableComponent.ts';
|
import { Confirmation } from '@/base/AppAwaitableComponent.ts';
|
||||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.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 { TypographyMaxLines } from '@/base/components/texts/TypographyMaxLines.tsx';
|
||||||
import { MigrationEntryMetadataText } from '@/features/migration/components/migration-entry/MigrationEntryMetadataText.tsx';
|
import { MigrationEntryMetadataText } from '@/features/migration/components/migration-entry/MigrationEntryMetadataText.tsx';
|
||||||
import Typography from '@mui/material/Typography';
|
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 { Link as RouterLink } from 'react-router-dom';
|
||||||
import { useTheme } from '@mui/material/styles';
|
import { useTheme } from '@mui/material/styles';
|
||||||
import { applyStyles } from '@/base/utils/ApplyStyles.ts';
|
import { applyStyles } from '@/base/utils/ApplyStyles.ts';
|
||||||
|
import { MigrationEntryThumbnail } from '@/features/migration/components/migration-entry/MigrationEntryThumbnail.tsx';
|
||||||
|
|
||||||
const EntryStatus = ({
|
const EntryStatus = ({
|
||||||
sourceMangaId,
|
sourceMangaId,
|
||||||
@@ -153,21 +152,7 @@ const EntryData = (entry: MigrationMatch) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Link component={RouterLink} to={AppRoutes.manga.path(id)}>
|
<MigrationEntryThumbnail entry={entry} height={112} />
|
||||||
<ListCardAvatar
|
|
||||||
iconUrl={Mangas.getThumbnailUrl(entry)}
|
|
||||||
alt={title}
|
|
||||||
slots={{
|
|
||||||
avatarProps: {
|
|
||||||
sx: {
|
|
||||||
width: 'unset',
|
|
||||||
height: 112,
|
|
||||||
aspectRatio: '3 / 4',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
<Stack sx={{ minWidth: 0, flex: 1 }}>
|
<Stack sx={{ minWidth: 0, flex: 1 }}>
|
||||||
<Typography
|
<Typography
|
||||||
variant="overline"
|
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 { MigrationManager } from '@/features/migration/MigrationManager.ts';
|
||||||
import { MigrationEntryCardContent } from '@/features/migration/components/migration-entry/MigrationEntryCardContent.tsx';
|
import { MigrationEntryCardContent } from '@/features/migration/components/migration-entry/MigrationEntryCardContent.tsx';
|
||||||
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
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 { TypographyMaxLines } from '@/base/components/texts/TypographyMaxLines.tsx';
|
||||||
import { MigrationEntryMetadataText } from '@/features/migration/components/migration-entry/MigrationEntryMetadataText.tsx';
|
import { MigrationEntryMetadataText } from '@/features/migration/components/migration-entry/MigrationEntryMetadataText.tsx';
|
||||||
import { MUIUtil } from '@/lib/mui/MUI.util.ts';
|
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 from '@mui/material/Link';
|
||||||
import { Link as RouterLink } from 'react-router-dom';
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
import { MigrationEntryThumbnail } from '@/features/migration/components/migration-entry/MigrationEntryThumbnail.tsx';
|
||||||
|
|
||||||
export const MigrationMatchedEntry = memo(
|
export const MigrationMatchedEntry = memo(
|
||||||
({ sourceMangaId, entry }: { sourceMangaId: MangaIdInfo['id']; entry: MigrationMatch }) => {
|
({ sourceMangaId, entry }: { sourceMangaId: MangaIdInfo['id']; entry: MigrationMatch }) => {
|
||||||
@@ -36,25 +35,7 @@ export const MigrationMatchedEntry = memo(
|
|||||||
<MigrationEntryCardContent>
|
<MigrationEntryCardContent>
|
||||||
{(() => (
|
{(() => (
|
||||||
<>
|
<>
|
||||||
<Link
|
<MigrationEntryThumbnail entry={entry} height={80} />
|
||||||
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>
|
|
||||||
<Stack sx={{ minWidth: 0, flex: 1 }}>
|
<Stack sx={{ minWidth: 0, flex: 1 }}>
|
||||||
<Typography variant="overline" color="textSecondary">
|
<Typography variant="overline" color="textSecondary">
|
||||||
{entry.sourceTitle}
|
{entry.sourceTitle}
|
||||||
|
|||||||
@@ -1915,6 +1915,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/features/manga/components/details/MangaDetails.tsx
|
#: src/features/manga/components/details/MangaDetails.tsx
|
||||||
#: src/features/manga/components/MangaBadges.tsx
|
#: src/features/manga/components/MangaBadges.tsx
|
||||||
|
#: src/features/migration/components/migration-entry/MigrationEntryThumbnail.tsx
|
||||||
msgid "In Library"
|
msgid "In Library"
|
||||||
msgstr "In Library"
|
msgstr "In Library"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user