diff --git a/CHANGELOG.md b/CHANGELOG.md index 07c3553e..128b5ace 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Added - (**Migration**) Add a search option to ignore outdated matches +- (**Migration**) Add a search option to ignore matches with missing chapters ### Changed diff --git a/src/features/manga/components/cards/MangaCard.tsx b/src/features/manga/components/cards/MangaCard.tsx index a21291c2..91ef6c95 100644 --- a/src/features/manga/components/cards/MangaCard.tsx +++ b/src/features/manga/components/cards/MangaCard.tsx @@ -109,7 +109,7 @@ export const MangaCard = memo((props: MangaCardProps) => { if (isMigrateSelectMode) { const isBulkMigrationManualSearch = !!onMigrateSelect; if (isBulkMigrationManualSearch) { - onMigrateSelect(manga); + onMigrateSelect({ ...manga, missingChapters: undefined }); return; } diff --git a/src/features/migration/Migration.types.ts b/src/features/migration/Migration.types.ts index ec8cb674..9103003f 100644 --- a/src/features/migration/Migration.types.ts +++ b/src/features/migration/Migration.types.ts @@ -73,6 +73,7 @@ export interface MigrationBulkSearchSettings { selectHighestChapterNumberSource: boolean; ignoreOutdatedMatches: boolean; requireAdditionalChapters: boolean; + ignoreWithMissingChapters: boolean; performAdvancedSearch: boolean; } @@ -102,6 +103,7 @@ export interface MigrationMatch extends MangaIdInfo, MangaTitleInfo, MangaThumbnailInfo, MangaSourceIdInfo, MangaArtistInfo, MangaAuthorInfo { sourceTitle: SourceDisplayNameInfo['displayName'] | undefined; latestChapterNumber: ChapterNumberInfo['chapterNumber'] | undefined; + missingChapters: number | undefined; } export interface TMigrationEntry { @@ -110,6 +112,7 @@ export interface TMigrationEntry { mangaArtist: MangaArtistInfo['artist']; mangaAuthor: MangaAuthorInfo['author']; latestChapterNumber: ChapterNumberInfo['chapterNumber'] | undefined; + missingChapters: number | undefined; mangaThumbnailUrl: MangaThumbnailInfo['thumbnailUrl'] | undefined; sourceId: SourceIdInfo['id']; sourceTitle: SourceDisplayNameInfo['displayName'] | undefined; diff --git a/src/features/migration/MigrationManager.ts b/src/features/migration/MigrationManager.ts index a8ea2706..d2860601 100644 --- a/src/features/migration/MigrationManager.ts +++ b/src/features/migration/MigrationManager.ts @@ -32,6 +32,7 @@ import { import { requestManager } from '@/lib/requests/RequestManager.ts'; import { GET_MIGRATION_SOURCE_MANGAS_FETCH } from '@/lib/graphql/source/SourceMutation.ts'; import type { + ChapterListFieldsFragment, GetMigrationSourceMangasFetchMutation, GetMigrationSourceMangasFetchMutationVariables, GetServerSettingsQuery, @@ -67,6 +68,7 @@ import { getErrorMessage } from '@/lib/HelperFunctions.ts'; import isEqual from 'lodash/fp/isEqual'; import uniqBy from 'lodash/fp/uniqBy'; import { MigrationEntries } from '@/features/migration/MigrationEntries.ts'; +import { Chapters } from '@/features/chapter/services/Chapters.ts'; const RESUMABLE_PHASES: readonly MigrationPhase[] = [MigrationPhase.SEARCHING, MigrationPhase.MIGRATING]; @@ -246,6 +248,7 @@ export class MigrationManager { author: cachedEntry?.author ?? searchMatch.author, sourceTitle: cachedEntry?.source?.displayName ?? searchMatch.sourceTitle, latestChapterNumber: cachedEntry?.highestNumberedChapter?.chapterNumber ?? searchMatch.latestChapterNumber, + missingChapters: searchMatch.missingChapters, }; } @@ -262,6 +265,7 @@ export class MigrationManager { mangaArtist: cachedEntry?.artist ?? entry.mangaArtist, mangaAuthor: cachedEntry?.author ?? entry.mangaAuthor, latestChapterNumber: cachedEntry?.highestNumberedChapter?.chapterNumber ?? entry.latestChapterNumber, + missingChapters: entry.missingChapters, mangaThumbnailUrl: cachedEntry?.thumbnailUrl ?? entry.mangaThumbnailUrl, sourceId: cachedEntry?.sourceId ?? entry.sourceId, sourceTitle: cachedEntry?.source?.displayName ?? entry.sourceTitle, @@ -336,6 +340,7 @@ export class MigrationManager { mangaArtist: manga.artist, mangaAuthor: manga.author, latestChapterNumber: manga.highestNumberedChapter?.chapterNumber, + missingChapters: undefined, mangaThumbnailUrl: manga.thumbnailUrl, sourceId: manga.sourceId, sourceTitle: manga.source?.displayName, @@ -710,7 +715,7 @@ export class MigrationManager { sourceId: SourceIdInfo['id'], signal: AbortSignal, { selectHighestChapterNumberSource, performAdvancedSearch }: MigrationBulkSearchSettings, - ): Promise { + ): Promise<{ manga: MangaMigrationFieldsFragment; chapters: ChapterListFieldsFragment[] | null }[]> { if (signal.aborted) { throw new Error(signal.reason); } @@ -781,12 +786,20 @@ export class MigrationManager { }).response, ); - return updatedMatch.data?.fetchManga?.manga ?? match; + if (updatedMatch.data?.fetchManga?.manga) { + return { + manga: updatedMatch.data.fetchManga.manga, + chapters: updatedMatch.data.fetchChapters?.chapters ?? null, + }; + } } catch (e) { // ignore } - return match; + return { + manga: match, + chapters: null, + }; })(); }); @@ -801,7 +814,12 @@ export class MigrationManager { mainSignal: AbortSignal, options: MigrationBulkSearchSettings, ): Promise { - const { selectHighestChapterNumberSource, ignoreOutdatedMatches, requireAdditionalChapters } = options; + const { + selectHighestChapterNumberSource, + ignoreOutdatedMatches, + requireAdditionalChapters, + ignoreWithMissingChapters, + } = options; const state = MigrationManager.getState(); const entry = state.entries[mangaId]; @@ -873,9 +891,11 @@ export class MigrationManager { : null; const newMatches = foundMatches.filter((newMatch) => - draftEntry.searchMatches.every((existingMatch) => newMatch.id !== existingMatch.id), + draftEntry.searchMatches.every( + (existingMatch) => newMatch.manga.id !== existingMatch.id, + ), ); - const matches = newMatches.map((manga) => ({ + const matches = newMatches.map(({ manga, chapters }) => ({ id: manga.id, title: manga.title, artist: manga.artist, @@ -884,6 +904,7 @@ export class MigrationManager { thumbnailUrl: manga.thumbnailUrl, sourceId: manga.sourceId, sourceTitle: manga.source?.displayName, + missingChapters: chapters ? Chapters.getMissingCount(chapters) : undefined, })); draftEntry.destSourceIdToSearchState[destSourceId] = true; @@ -921,6 +942,8 @@ export class MigrationManager { const ignoreOutdatedMatch = ignoreOutdatedMatches && isOutdated; const satisfiesRequireAdditionalChapters = !requireAdditionalChapters || hasNewerChapter; + const ignoreBecauseMissingChapters = + ignoreWithMissingChapters && !!bestMatch.missingChapters; const isPreferredSourcePriorityMatch = hasHigherSourcePriority && !selectHighestChapterNumberSource; const isPreferredChapterNumberMatch = @@ -933,6 +956,7 @@ export class MigrationManager { !draftEntry.isManualSelection && !ignoreOutdatedMatch && satisfiesRequireAdditionalChapters && + !ignoreBecauseMissingChapters && (isPreferredSourcePriorityMatch || isPreferredChapterNumberMatch); if (isPreferredMatch) { draftEntry.selectedMatchMangaId = bestMatch.id; diff --git a/src/features/migration/components/MigrationBulkSearchOptionsDialog.tsx b/src/features/migration/components/MigrationBulkSearchOptionsDialog.tsx index 2e5977d2..4314588b 100644 --- a/src/features/migration/components/MigrationBulkSearchOptionsDialog.tsx +++ b/src/features/migration/components/MigrationBulkSearchOptionsDialog.tsx @@ -30,6 +30,7 @@ export const MigrationBulkSearchOptionsDialog = ({ const [selectHighestChapterNumberSource, setSelectHighestChapterNumberSource] = useState(false); const [ignoreOutdatedMatches, setIgnoreOutdatedMatches] = useState(false); + const [ignoreWithMissingChapters, setIgnoreWithMissingChapters] = useState(false); const [requireAdditionalChapters, setRequireAdditionalChapters] = useState(false); const [performAdvancedSearch, setPerformAdvancedSearch] = useState(false); @@ -49,7 +50,7 @@ export const MigrationBulkSearchOptionsDialog = ({ {t`Only automatically select matches if they have at least the same latest chapter. They will still be shown in the found matches`} + >{t`Automatically select matches if they have at least the same latest chapter`} } sx={{ @@ -70,7 +71,7 @@ export const MigrationBulkSearchOptionsDialog = ({ {t`Only automatically select matches if they have additional chapters. They will still be shown in the found matches`} + >{t`Automatically select matches if they have additional chapters`} } sx={{ @@ -79,6 +80,27 @@ export const MigrationBulkSearchOptionsDialog = ({ checked={requireAdditionalChapters} onChange={(_, checked) => setRequireAdditionalChapters(checked)} /> + + {t`Ignore matches with missing chapters`} + {t`Automatically select matches if they have no missing chapters`} + + } + sx={{ + alignItems: 'start', + }} + checked={ignoreWithMissingChapters} + onChange={(_, checked) => setIgnoreWithMissingChapters(checked)} + /> , + entry: Pick, ) => { const { t } = useLingui(); const latestChapterNumber = (entry.latestChapterNumber ?? 0) > 1 ? entry.latestChapterNumber : t`Unknown`; - const latestChapter = t`Latest: ${latestChapterNumber}`; + const isSameArtistAuthor = entry.artist === entry.author; - const artist = entry.artist ? `${entry.artist} - ` : ''; - const author = entry.author ? `${entry.author} - ` : ''; - const isSameArtistAuthor = artist === author; - const artistAuthor = isSameArtistAuthor ? artist : `${artist}${author}`; + const nodes = [ + entry.author && ( + + {entry.author} + + ), + !isSameArtistAuthor && entry.artist ? ( + + {entry.artist} + + ) : null, + + {t`Latest: ${latestChapterNumber}`} + , + entry.missingChapters ? ( + + {t`Missing: ${entry.missingChapters}`} + + ) : null, + ] + .filter((node) => node !== null) + .map((node, index, array) => ( + <> + {node} + {index < array.length - 1 && ' - '} + + )); return ( - {artistAuthor} - {latestChapter} + {nodes} ); }; diff --git a/src/features/migration/components/migration-entry/MigrationSourceEntry.tsx b/src/features/migration/components/migration-entry/MigrationSourceEntry.tsx index f45a0f1d..188c6bae 100644 --- a/src/features/migration/components/migration-entry/MigrationSourceEntry.tsx +++ b/src/features/migration/components/migration-entry/MigrationSourceEntry.tsx @@ -31,6 +31,7 @@ export const MigrationSourceEntry = memo((entry: TMigrationEntry) => { mangaArtist, mangaAuthor, latestChapterNumber, + missingChapters, searchMatches, status, } = entry; @@ -50,6 +51,7 @@ export const MigrationSourceEntry = memo((entry: TMigrationEntry) => { artist={mangaArtist} author={mangaAuthor} latestChapterNumber={latestChapterNumber} + missingChapters={missingChapters} /> @@ -91,6 +93,7 @@ export const MigrationSourceEntry = memo((entry: TMigrationEntry) => { artist={mangaArtist} author={mangaAuthor} latestChapterNumber={latestChapterNumber} + missingChapters={missingChapters} /> diff --git a/src/i18n/locales/en.po b/src/i18n/locales/en.po index eb495e4d..0718b968 100644 --- a/src/i18n/locales/en.po +++ b/src/i18n/locales/en.po @@ -518,6 +518,18 @@ msgstr "Automatically mark chapters as read when you download them." msgid "Automatically refresh metadata" msgstr "Automatically refresh metadata" +#: src/features/migration/components/MigrationBulkSearchOptionsDialog.tsx +msgid "Automatically select matches if they have additional chapters" +msgstr "Automatically select matches if they have additional chapters" + +#: src/features/migration/components/MigrationBulkSearchOptionsDialog.tsx +msgid "Automatically select matches if they have at least the same latest chapter" +msgstr "Automatically select matches if they have at least the same latest chapter" + +#: src/features/migration/components/MigrationBulkSearchOptionsDialog.tsx +msgid "Automatically select matches if they have no missing chapters" +msgstr "Automatically select matches if they have no missing chapters" + #: src/features/reader/settings/behaviour/ReaderBehaviourSettings.tsx msgid "Automatically use webtoon mode for entries that are detected to likely use the long strip format" msgstr "Automatically use webtoon mode for entries that are detected to likely use the long strip format" @@ -1845,6 +1857,10 @@ msgstr "Ignore filters when searching" msgid "Ignore matches that are behind in chapters" msgstr "Ignore matches that are behind in chapters" +#: src/features/migration/components/MigrationBulkSearchOptionsDialog.tsx +msgid "Ignore matches with missing chapters" +msgstr "Ignore matches with missing chapters" + #: src/features/migration/components/MigrationBulkSearchOptionsDialog.tsx msgid "Ignore matches without newer chapters" msgstr "Ignore matches without newer chapters" @@ -2375,6 +2391,11 @@ msgstr "Minute" msgid "Misc" msgstr "Misc" +#. placeholder {0}: entry.missingChapters +#: src/features/migration/components/migration-entry/MigrationEntryMetadataText.tsx +msgid "Missing: {0}" +msgstr "Missing: {0}" + #: src/features/reader/overlay/settings/ReaderSettingOverlayMode.tsx msgid "Mobile" msgstr "Mobile" @@ -2558,14 +2579,6 @@ msgstr "Oneshot" msgid "Ongoing" msgstr "Ongoing" -#: src/features/migration/components/MigrationBulkSearchOptionsDialog.tsx -msgid "Only automatically select matches if they have additional chapters. They will still be shown in the found matches" -msgstr "Only automatically select matches if they have additional chapters. They will still be shown in the found matches" - -#: src/features/migration/components/MigrationBulkSearchOptionsDialog.tsx -msgid "Only automatically select matches if they have at least the same latest chapter. They will still be shown in the found matches" -msgstr "Only automatically select matches if they have at least the same latest chapter. They will still be shown in the found matches" - #: src/features/migration/Migration.constants.ts msgid "Only outdated matches found" msgstr "Only outdated matches found"