From 278ebb312f3d9f7339db464323b2fa388d56c40d Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Wed, 27 May 2026 22:28:28 +0200 Subject: [PATCH] Fix selecting migration destination from source browse search page When going to the source search from the global search, the manga card mode wasn't passed, which prevented a manga from getting selected as the migration destination #1110 --- CHANGELOG.md | 1 + src/features/global-search/SearchAll.types.ts | 3 +++ .../global-search/screens/SearchAll.tsx | 26 +++++++++++++------ src/features/manga/Manga.types.ts | 2 +- .../manga/components/cards/MangaCard.tsx | 23 +++++++--------- src/features/migration/MigrationManager.ts | 1 + .../screens/MigrationSelectMangas.tsx | 2 +- src/features/source/Source.types.ts | 3 ++- .../source/browse/screens/SourceMangas.tsx | 18 ++++++++++++- 9 files changed, 54 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98736a13..df14f4c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - (**Migration**) Fix search hotkey (ctrl+f) in the single manga migration search page not focusing the search textfield - (**Migration**) Fix missing manual search option for in progress entry search without a selected match on mobile - (**Migration**) Fix being unable to retry failed search for a match without a selected match +- (**Migration**) Fix selecting a destination from a source browse search page - (**Reader**) Fix scrollbar appearing with "fit to widt/height/screen" page scale mode and applied safe area insets - (**Reader**) Fix wrongly positioned mobile progress bar current page indicator - (**Reader**) Fix mobile progress bar previous/next chapter button visibility on hover and while disabled diff --git a/src/features/global-search/SearchAll.types.ts b/src/features/global-search/SearchAll.types.ts index da726b44..84acbcc3 100644 --- a/src/features/global-search/SearchAll.types.ts +++ b/src/features/global-search/SearchAll.types.ts @@ -6,7 +6,10 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import type { MangaCardMode } from '@/features/manga/Manga.types.ts'; + export interface RouteStateSourcesSearchAll { title?: string; shouldShowOnlyPinnedSources?: boolean; + mode?: MangaCardMode; } diff --git a/src/features/global-search/screens/SearchAll.tsx b/src/features/global-search/screens/SearchAll.tsx index 2f49dbc3..ef0b9865 100644 --- a/src/features/global-search/screens/SearchAll.tsx +++ b/src/features/global-search/screens/SearchAll.tsx @@ -26,7 +26,7 @@ import { useLingui } from '@lingui/react/macro'; import { requestManager } from '@/lib/requests/RequestManager.ts'; import { AppbarSearch } from '@/base/components/AppbarSearch.tsx'; import { useDebounce } from '@/base/hooks/useDebounce.ts'; -import type { MangaCardProps } from '@/features/manga/Manga.types.ts'; +import type { MangaCardProps, MangaIdInfo } from '@/features/manga/Manga.types.ts'; import { EmptyView } from '@/base/components/feedback/EmptyView.tsx'; import { STABLE_EMPTY_ARRAY, STABLE_EMPTY_OBJECT } from '@/base/Base.constants.ts'; import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; @@ -59,7 +59,6 @@ import { SearchParam } from '@/base/Base.types.ts'; import { MigrationManager } from '@/features/migration/MigrationManager.ts'; import { assertIsDefined } from '@/base/Asserts.ts'; import { ReactRouter } from '@/lib/react-router/ReactRouter.ts'; -import { SubpathUtil } from '@/lib/utils/SubpathUtil.ts'; import type { RouteStateSourcesSearchAll } from '@/features/global-search/SearchAll.types.ts'; type SourceLoadingState = { isLoading: boolean; hasResults: boolean; emptySearch: boolean; error: any }; @@ -128,11 +127,13 @@ const SourceSearchPreview = React.memo( mode, shouldShowOnlySourcesWithResults, onMigrateSelect, + mangaId, }: { source: SourceIdInfo & SourceDisplayNameInfo & SourceNameInfo & SourceLanguageInfo; onSearchRequestFinished: (source: SourceIdInfo, state: SourceLoadingState) => void; searchString: string | null | undefined; emptyQuery: boolean; + mangaId?: MangaIdInfo['id']; } & Pick & Pick) => { const { t } = useLingui(); @@ -194,6 +195,10 @@ const SourceSearchPreview = React.memo( @@ -254,8 +259,6 @@ export const SearchAll = ({ const { state } = useLocation(); const { ref: filterHeaderRef, height: filterHeaderHeight } = useElementSize(); - const isMigrateMode = SubpathUtil.getPathname().startsWith(AppRoutes.migrate.path); - const { mangaId } = useParams<{ mangaId?: string }>() ?? STABLE_EMPTY_OBJECT; const [query] = useQueryParam(SearchParam.QUERY, StringParam); const searchString = useDebounce(query, TRIGGER_SEARCH_THRESHOLD); @@ -281,7 +284,13 @@ export const SearchAll = ({ const sourceLanguages = useMemo(() => Sources.getLanguages(sources, { excludeLocalSource: true }), [sources]); const hasPinnedSources = useMemo(() => !!Sources.filter(sources, { pinned: true }).length, [sources]); - const shouldShowOnlyPinnedSources = state?.shouldShowOnlyPinnedSources ?? hasPinnedSources; + + const { + title = t`Global Search`, + shouldShowOnlyPinnedSources = hasPinnedSources, + mode = 'source', + } = state ?? STABLE_EMPTY_OBJECT; + const isMigrateMode = ['migrate.select.single', 'migrate.select.bulk'].includes(mode); const filteredSources = useMemo( () => @@ -319,7 +328,7 @@ export const SearchAll = ({ ); useAppTitleAndAction( - isMigrateMode ? state?.title : t`Global Search`, + title, <> { assertIsDefined(mangaId); MigrationManager.selectManualMatch(Number(mangaId), { @@ -444,6 +453,7 @@ export const SearchAll = ({ } : undefined } + mangaId={mangaId ? Number(mangaId) : undefined} /> ))} diff --git a/src/features/manga/Manga.types.ts b/src/features/manga/Manga.types.ts index 364e1657..628d13ba 100644 --- a/src/features/manga/Manga.types.ts +++ b/src/features/manga/Manga.types.ts @@ -30,7 +30,7 @@ import type { TrackRecordType, } from '@/lib/graphql/generated/graphql-base.types.ts'; -export type MangaCardMode = 'default' | 'source' | 'migrate.search' | 'migrate.select' | 'duplicate'; +export type MangaCardMode = 'default' | 'source' | 'migrate.select.bulk' | 'migrate.select.single' | 'duplicate'; type MangaCardBaseProps = Pick & Omit & diff --git a/src/features/manga/components/cards/MangaCard.tsx b/src/features/manga/components/cards/MangaCard.tsx index d437bebb..f6982547 100644 --- a/src/features/manga/components/cards/MangaCard.tsx +++ b/src/features/manga/components/cards/MangaCard.tsx @@ -31,21 +31,16 @@ import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler'; import { MangaMigration } from '@/features/migration/MangaMigration.ts'; import { MANGA_ACTION_TO_TRANSLATION } from '@/features/manga/Manga.constants.ts'; import { getErrorMessage } from '@/lib/HelperFunctions.ts'; +import { assertIsDefined } from '@/base/Asserts.ts'; -const getMangaLinkTo = ( - mode: MangaCardMode, - mangaId: number, - sourceId: string | undefined, - mangaTitle: string, -): string => { +const getMangaLinkTo = (mode: MangaCardMode, mangaId: number): string => { switch (mode) { case 'default': case 'source': case 'duplicate': return AppRoutes.manga.path(mangaId); - case 'migrate.search': - return AppRoutes.migrate.children.singleMangaSearch.path(sourceId ?? '-1', mangaId, mangaTitle); - case 'migrate.select': + case 'migrate.select.single': + case 'migrate.select.bulk': return ''; default: throw new Error(`getMangaLinkTo: unexpected MangaCardMode "${mode}"`); @@ -73,13 +68,15 @@ export const MangaCard = memo((props: MangaCardProps) => { const { updateLibraryState, isInLibrary } = useManageMangaLibraryState(manga, mode === 'source'); - const mangaLinkTo = getMangaLinkTo(mode, manga.id, manga.sourceId, manga.title); + const mangaLinkTo = getMangaLinkTo(mode, manga.id); const handleClick = useCallback( (event: React.MouseEvent | React.TouchEvent, openMenu?: () => void) => { const isDefaultMode = mode === 'default'; const isSourceMode = mode === 'source'; - const isMigrateSelectMode = mode === 'migrate.select'; + const isMigrationSelectSingleMode = mode === 'migrate.select.single'; + const isMigrationSelectBulkMode = mode === 'migrate.select.bulk'; + const isMigrateSelectMode = isMigrationSelectSingleMode || isMigrationSelectBulkMode; const isSelectionMode = selected !== null; const isLongPress = !!openMenu; @@ -107,8 +104,8 @@ export const MangaCard = memo((props: MangaCardProps) => { } if (isMigrateSelectMode) { - const isBulkMigrationManualSearch = !!onMigrateSelect; - if (isBulkMigrationManualSearch) { + if (isMigrationSelectBulkMode) { + assertIsDefined(onMigrateSelect); onMigrateSelect({ ...manga, missingChapters: undefined }); return; } diff --git a/src/features/migration/MigrationManager.ts b/src/features/migration/MigrationManager.ts index 8307d99b..eb112261 100644 --- a/src/features/migration/MigrationManager.ts +++ b/src/features/migration/MigrationManager.ts @@ -323,6 +323,7 @@ export class MigrationManager { { state: AppRoutes.migrate.children.singleMangaSearch.state({ title: t`Migrate "${manga.title}"`, + mode: 'migrate.select.single', }), }, ); diff --git a/src/features/migration/screens/MigrationSelectMangas.tsx b/src/features/migration/screens/MigrationSelectMangas.tsx index c120d5dd..e6951689 100644 --- a/src/features/migration/screens/MigrationSelectMangas.tsx +++ b/src/features/migration/screens/MigrationSelectMangas.tsx @@ -164,7 +164,7 @@ export const MigrationSelectMangas = () => { return ( <> ().state ?? STABLE_EMPTY_OBJECT; const { @@ -485,8 +489,20 @@ export function SourceMangas() { messageExtra={messageExtra} isLoading={isLoading} gridLayout={sourceGridLayout} - mode="source" + mode={mode} inLibraryIndicator + onMigrateSelect={ + mangaId + ? (match) => { + MigrationManager.selectManualMatch(mangaId, { + ...match, + sourceTitle: Sources.getFromCache(match.sourceId)?.displayName, + latestChapterNumber: undefined, + }); + ReactRouter.navigate(AppRoutes.migrate.path); + } + : undefined + } /> )} {error && (