From 4a9f90b74662cf35d0b92043f8a59d312e07f19c Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Mon, 21 Apr 2025 17:00:13 +0200 Subject: [PATCH] Introduce "sources" util class --- .../global-search/screens/SearchAll.tsx | 12 ++++++---- src/modules/manga/components/MangaDetails.tsx | 3 +-- .../metadata/services/MetadataMigrations.ts | 6 ++--- .../metadata/services/MetadataReader.ts | 6 ++--- .../metadata/services/MetadataUpdater.ts | 19 ++++++++------- src/modules/source/Source.types.ts | 3 +++ src/modules/source/components/SourceCard.tsx | 23 ++++++++----------- src/modules/source/screens/SourceMangas.tsx | 8 +++---- src/modules/source/screens/Sources.tsx | 9 ++++---- src/modules/source/services/SourceMetadata.ts | 17 +++++--------- src/modules/source/services/Sources.ts | 17 ++++++++++++++ 11 files changed, 67 insertions(+), 56 deletions(-) create mode 100644 src/modules/source/services/Sources.ts diff --git a/src/modules/global-search/screens/SearchAll.tsx b/src/modules/global-search/screens/SearchAll.tsx index 44b568c9..db827a70 100644 --- a/src/modules/global-search/screens/SearchAll.tsx +++ b/src/modules/global-search/screens/SearchAll.tsx @@ -31,6 +31,8 @@ import { translateExtensionLanguage } from '@/modules/extension/Extensions.utils import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; import { getErrorMessage } from '@/lib/HelperFunctions.ts'; import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx'; +import { Sources } from '@/modules/source/services/Sources.ts'; +import { SourceIdInfo } from '@/modules/source/Source.types.ts'; type SourceLoadingState = { isLoading: boolean; hasResults: boolean; emptySearch: boolean }; type SourceToLoadingStateMap = Map; @@ -62,8 +64,8 @@ const compareSourceByName = ( }; const compareSourcesBySearchResult = ( - sourceA: Pick, - sourceB: Pick, + sourceA: SourceIdInfo, + sourceB: SourceIdInfo, sourceToFetchedStateMap: SourceToLoadingStateMap, ): -1 | 0 | 1 => { const isSourceAFetched = !sourceToFetchedStateMap.get(sourceA.id)?.isLoading; @@ -101,7 +103,7 @@ const SourceSearchPreview = React.memo( }: { source: Pick; onSearchRequestFinished: ( - source: Pick, + source: SourceIdInfo, isLoading: boolean, hasResults: boolean, emptySearch: boolean, @@ -217,7 +219,7 @@ export const SearchAll: React.FC = () => { const sourcesSortedByName = useMemo(() => [...sources].sort(compareSourceByName), [sources]); const sourcesFilteredByLang = useMemo( - () => sourcesSortedByName.filter((source) => shownLangs.includes(source.lang) || Number(source.id) === 0), + () => sourcesSortedByName.filter((source) => shownLangs.includes(source.lang) || Sources.isLocalSource(source)), [sourcesSortedByName, shownLangs], ); const sourcesFilteredByNsfw = useMemo( @@ -233,7 +235,7 @@ export const SearchAll: React.FC = () => { ); const updateSourceLoadingState = useCallback( - ({ id }: Pick, isLoading: boolean, hasResults: boolean, emptySearch: boolean) => { + ({ id }: SourceIdInfo, isLoading: boolean, hasResults: boolean, emptySearch: boolean) => { setSourceToLoadingStateMap((currentMap) => { const mapCopy = new Map(currentMap); mapCopy.set(id, { isLoading, hasResults, emptySearch }); diff --git a/src/modules/manga/components/MangaDetails.tsx b/src/modules/manga/components/MangaDetails.tsx index ba81f175..c3126254 100644 --- a/src/modules/manga/components/MangaDetails.tsx +++ b/src/modules/manga/components/MangaDetails.tsx @@ -153,8 +153,7 @@ function getSourceName(source?: Pick | null): return translate('global.label.unknown'); } - const isLocalSource = Number(source.id) === 0; - if (isLocalSource) { + if (Sources.isLocalSource(source)) { return translate('source.local_source.title'); } diff --git a/src/modules/metadata/services/MetadataMigrations.ts b/src/modules/metadata/services/MetadataMigrations.ts index 77eeb98c..a2899d06 100644 --- a/src/modules/metadata/services/MetadataMigrations.ts +++ b/src/modules/metadata/services/MetadataMigrations.ts @@ -24,9 +24,9 @@ import { extractOriginalKey, getMetadataKey } from '@/modules/metadata/Metadata. import { MangaIdInfo } from '@/modules/manga/Manga.types.ts'; import { ChapterIdInfo } from '@/modules/chapter/services/Chapters.ts'; import { CategoryIdInfo } from '@/modules/category/Category.types.ts'; -import { SourceType } from '@/lib/graphql/generated/graphql.ts'; import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; import { getMetadataDeleteFunction, getMetadataUpdateFunction } from '@/modules/metadata/services/MetadataUpdater.ts'; +import { SourceIdInfo } from '@/modules/source/Source.types.ts'; const getAppKeyPrefixForMigration = (migrationId: number): string => { const appKeyPrefix = METADATA_MIGRATIONS.slice(0, migrationId) @@ -269,7 +269,7 @@ const commitMigratedMetadata = ( | (MangaIdInfo & MetadataHolder) | (ChapterIdInfo & MetadataHolder) | (CategoryIdInfo & MetadataHolder) - | (Pick & MetadataHolder) + | (SourceIdInfo & MetadataHolder) | undefined, migratedMetadata: Metadata, useEffectFn: typeof useEffect = (fn: () => void) => fn(), @@ -334,7 +334,7 @@ export const applyMetadataMigrations = ( | (MangaIdInfo & MetadataHolder) | (ChapterIdInfo & MetadataHolder) | (CategoryIdInfo & MetadataHolder) - | (Pick & MetadataHolder), + | (SourceIdInfo & MetadataHolder), useEffectFn: typeof useEffect = (fn: () => void) => fn(), ): Metadata | undefined => { const meta = { ...(metadataHolder?.meta ?? {}) }; diff --git a/src/modules/metadata/services/MetadataReader.ts b/src/modules/metadata/services/MetadataReader.ts index bdf73dab..a4cbfe1e 100644 --- a/src/modules/metadata/services/MetadataReader.ts +++ b/src/modules/metadata/services/MetadataReader.ts @@ -19,9 +19,9 @@ import { MangaIdInfo } from '@/modules/manga/Manga.types.ts'; import { ChapterIdInfo } from '@/modules/chapter/services/Chapters.ts'; import { CategoryIdInfo } from '@/modules/category/Category.types.ts'; -import { SourceType } from '@/lib/graphql/generated/graphql.ts'; import { doesMetadataKeyExistIn, getMetadataKey } from '@/modules/metadata/Metadata.utils.ts'; import { applyMetadataMigrations } from '@/modules/metadata/services/MetadataMigrations.ts'; +import { SourceIdInfo } from '@/modules/source/Source.types.ts'; const getMetadataValueFrom = ( metadata: Metadata | undefined, @@ -70,7 +70,7 @@ export function getMetadataFrom>>( type: 'source', - metadataHolder: Pick & MetadataHolder, + metadataHolder: SourceIdInfo & MetadataHolder, metadataWithDefaultValues: METADATA, prefixes?: string[], useEffectFn?: typeof useEffect, @@ -82,7 +82,7 @@ export function getMetadataFrom & MetadataHolder), + | (SourceIdInfo & MetadataHolder), metadataWithDefaultValues: METADATA, prefixes?: string[], useEffectFn: typeof useEffect = (fn: () => void) => fn(), diff --git a/src/modules/metadata/services/MetadataUpdater.ts b/src/modules/metadata/services/MetadataUpdater.ts index a04754fd..d5705eeb 100644 --- a/src/modules/metadata/services/MetadataUpdater.ts +++ b/src/modules/metadata/services/MetadataUpdater.ts @@ -7,7 +7,6 @@ */ import { requestManager } from '@/lib/requests/RequestManager.ts'; -import { SourceType } from '@/lib/graphql/generated/graphql.ts'; import { ChapterIdInfo } from '@/modules/chapter/services/Chapters.ts'; import { CategoryIdInfo } from '@/modules/category/Category.types.ts'; import { @@ -21,6 +20,7 @@ import { import { MangaIdInfo } from '@/modules/manga/Manga.types.ts'; import { getMetadataKey } from '@/modules/metadata/Metadata.utils.ts'; import { convertToGqlMeta } from '@/modules/metadata/services/MetadataConverter.ts'; +import { SourceIdInfo } from '@/modules/source/Source.types.ts'; const requestUpdateMetadataValue = async ( metadataHolder: GqlMetaHolder, @@ -46,8 +46,7 @@ const requestUpdateMetadataValue = async ( await requestManager.setMangaMeta((metadataHolder as MangaIdInfo).id, metadataKey, value).response; break; case 'source': - await requestManager.setSourceMeta((metadataHolder as Pick).id, metadataKey, value) - .response; + await requestManager.setSourceMeta((metadataHolder as SourceIdInfo).id, metadataKey, value).response; break; default: throw new Error(`requestUpdateMetadataValue: unknown holderType "${holderType}"`); @@ -95,7 +94,7 @@ export const requestUpdateCategoryMetadata = async ( ): Promise => requestUpdateMetadata(category, 'category', keysToValues, keyPrefixes, isMetadataKey); export const requestUpdateSourceMetadata = async ( - source: Pick & GqlMetaHolder, + source: SourceIdInfo & GqlMetaHolder, keysToValue: MetadataKeyValuePair[], keyPrefixes?: string[], isMetadataKey?: boolean, @@ -108,7 +107,7 @@ export const getMetadataUpdateFunction = ( | (MangaIdInfo & MetadataHolder) | (ChapterIdInfo & MetadataHolder) | (CategoryIdInfo & MetadataHolder) - | (Pick & MetadataHolder), + | (SourceIdInfo & MetadataHolder), ): ((keyValuePair: MetadataKeyValuePair[], keyPrefixes?: string[], isMetadataKey?: boolean) => Promise) => { switch (type) { case 'global': @@ -135,7 +134,7 @@ export const getMetadataUpdateFunction = ( return (...args) => requestUpdateSourceMetadata( { - id: (metadataHolder as Pick).id, + id: (metadataHolder as SourceIdInfo).id, meta: convertToGqlMeta(metadataHolder.meta), }, ...args, @@ -168,7 +167,7 @@ export const requestDeleteMetadataValue = async ( await requestManager.deleteMangaMeta((metadataHolder as MangaIdInfo).id, metadataKey).response; break; case 'source': - await requestManager.deleteSourceMeta((metadataHolder as Pick).id, metadataKey).response; + await requestManager.deleteSourceMeta((metadataHolder as SourceIdInfo).id, metadataKey).response; break; default: throw new Error(`requestDeleteMetadataValue: unknown holderType "${holderType}"`); @@ -215,7 +214,7 @@ export const requestDeleteCategoryMetadata = async ( ): Promise => requestDeleteMetadata(category, 'category', keys, keyPrefixes, isMetadataKey); export const requestDeleteSourceMetadata = async ( - source: Pick & GqlMetaHolder, + source: SourceIdInfo & GqlMetaHolder, keys: AppMetadataKeys[], keyPrefixes?: string[], isMetadataKey?: boolean, @@ -228,7 +227,7 @@ export const getMetadataDeleteFunction = ( | (MangaIdInfo & MetadataHolder) | (ChapterIdInfo & MetadataHolder) | (CategoryIdInfo & MetadataHolder) - | (Pick & MetadataHolder), + | (SourceIdInfo & MetadataHolder), ): ((metadataToDelete: AppMetadataKeys[], keyPrefixes?: string[], isMetadataKey?: boolean) => Promise) => { switch (type) { case 'global': @@ -255,7 +254,7 @@ export const getMetadataDeleteFunction = ( return (...args) => requestDeleteSourceMetadata( { - id: (metadataHolder as Pick).id, + id: (metadataHolder as SourceIdInfo).id, meta: convertToGqlMeta(metadataHolder.meta), }, ...args, diff --git a/src/modules/source/Source.types.ts b/src/modules/source/Source.types.ts index b7f41ada..eedc82bf 100644 --- a/src/modules/source/Source.types.ts +++ b/src/modules/source/Source.types.ts @@ -10,6 +10,7 @@ import { GetSourceBrowseQuery, GetSourceSettingsQuery, SourcePreferenceChangeInput, + SourceType, } from '@/lib/graphql/generated/graphql.ts'; export interface IPos { @@ -57,3 +58,5 @@ export type MultiSelectListPreferenceProps = PreferenceProps & export type EditTextPreferenceProps = PreferenceProps & ExtractByKeyValue; + +export type SourceIdInfo = Pick; diff --git a/src/modules/source/components/SourceCard.tsx b/src/modules/source/components/SourceCard.tsx index 81ecdaab..cc461fff 100644 --- a/src/modules/source/components/SourceCard.tsx +++ b/src/modules/source/components/SourceCard.tsx @@ -36,21 +36,18 @@ export const SourceCard: React.FC = (props: IProps) => { const isMobileWidth = MediaQuery.useIsMobileWidth(); + const { source, showSourceRepo } = props; const { - source: { - id, - name, - lang, - iconUrl, - supportsLatest, - isNsfw, - extension: { repo }, - }, - showSourceRepo, - } = props; + id, + name, + lang, + iconUrl, + supportsLatest, + isNsfw, + extension: { repo }, + } = source; - const isLocalSource = Number(id) === 0; - const sourceName = isLocalSource ? t('source.local_source.title') : name; + const sourceName = Sources.isLocalSource(source) ? t('source.local_source.title') : name; return ( = { id: '-1' }; +const DEFAULT_SOURCE: SourceIdInfo = { id: '-1' }; const ContentTypeMenu = styled('div')(({ theme }) => ({ display: 'flex', @@ -332,7 +332,7 @@ export function SourceMangas() { ); const message = !isLoading ? t(SOURCE_CONTENT_TYPE_TO_ERROR_MSG_KEY[contentType]) : undefined; - const isLocalSource = sourceId === '0'; + const isLocalSource = sourceId === Sources.LOCAL_SOURCE_ID; const messageExtra = isLocalSource ? ( <> {t('source.local_source.label.checkout')} diff --git a/src/modules/source/screens/Sources.tsx b/src/modules/source/screens/Sources.tsx index 62557990..7f0e1538 100644 --- a/src/modules/source/screens/Sources.tsx +++ b/src/modules/source/screens/Sources.tsx @@ -26,13 +26,13 @@ import { translateExtensionLanguage } from '@/modules/extension/Extensions.utils import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; import { getErrorMessage } from '@/lib/HelperFunctions.ts'; import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx'; +import { Sources as SourceService } from '@/modules/source/services/Sources.ts'; function sourceToLangList(sources: Pick[]) { const result = new Set(); sources.forEach((source) => { - const isLocalSource = Number(source.id) === 0; - const lang = isLocalSource ? DefaultLanguage.OTHER : source.lang; + const lang = SourceService.isLocalSource(source) ? DefaultLanguage.OTHER : source.lang; result.add(lang); }); @@ -46,8 +46,7 @@ function groupByLang>( const result: Record = {}; sources.forEach((source) => { - const isLocalSource = Number(source.id) === 0; - const lang = isLocalSource ? DefaultLanguage.OTHER : source.lang; + const lang = SourceService.isLocalSource(source) ? DefaultLanguage.OTHER : source.lang; result[lang] ??= []; result[lang].push(source); @@ -146,7 +145,7 @@ export function Sources() { .filter((source) => { const isLangOther = lang === DefaultLanguage.OTHER; if (isLangOther) { - const isLocalSource = Number(source.id) === 0; + const isLocalSource = SourceService.isLocalSource(source); const isLangShown = shownLangs.includes(lang); const isLangOtherSourceShown = isLangShown || isLocalSource; diff --git a/src/modules/source/services/SourceMetadata.ts b/src/modules/source/services/SourceMetadata.ts index 91f19185..666966d7 100644 --- a/src/modules/source/services/SourceMetadata.ts +++ b/src/modules/source/services/SourceMetadata.ts @@ -10,8 +10,7 @@ import { useEffect, useMemo } from 'react'; import { jsonSaveParse } from '@/lib/HelperFunctions.ts'; import { requestUpdateSourceMetadata } from '@/modules/metadata/services/MetadataUpdater.ts'; import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; -import { SourceType } from '@/lib/graphql/generated/graphql.ts'; -import { ISourceMetadata, SourceMetadataKeys } from '@/modules/source/Source.types.ts'; +import { ISourceMetadata, SourceIdInfo, SourceMetadataKeys } from '@/modules/source/Source.types.ts'; import { convertFromGqlMeta } from '@/modules/metadata/services/MetadataConverter.ts'; import { getMetadataFrom } from '@/modules/metadata/services/MetadataReader.ts'; import { @@ -39,10 +38,7 @@ const convertGqlMetadataToAppMetadata = ( savedSearches: jsonSaveParse(metadata.savedSearches as string) ?? undefined, }); -const getMetadata = ( - metaHolder: Pick & GqlMetaHolder, - useEffectFn?: typeof useEffect, -): ISourceMetadata => +const getMetadata = (metaHolder: SourceIdInfo & GqlMetaHolder, useEffectFn?: typeof useEffect): ISourceMetadata => convertGqlMetadataToAppMetadata( getMetadataFrom( 'source', @@ -53,10 +49,9 @@ const getMetadata = ( ), ); -export const getSourceMetadata = (metaHolder: Pick & GqlMetaHolder): ISourceMetadata => - getMetadata(metaHolder); +export const getSourceMetadata = (metaHolder: SourceIdInfo & GqlMetaHolder): ISourceMetadata => getMetadata(metaHolder); -export const useGetSourceMetadata = (metaHolder: Pick & GqlMetaHolder): ISourceMetadata => { +export const useGetSourceMetadata = (metaHolder: SourceIdInfo & GqlMetaHolder): ISourceMetadata => { const metadata = getMetadata(metaHolder, useEffect); return useMemo(() => metadata, [metaHolder]); }; @@ -65,7 +60,7 @@ export const updateSourceMetadata = async < MetadataKeys extends SourceMetadataKeys = SourceMetadataKeys, MetadataKey extends MetadataKeys = MetadataKeys, >( - source: Pick & GqlMetaHolder, + source: SourceIdInfo & GqlMetaHolder, metadataKey: MetadataKey, value: ISourceMetadata[MetadataKey], ): Promise => @@ -75,7 +70,7 @@ export const updateSourceMetadata = async < export const createUpdateSourceMetadata = ( - source: Pick & GqlMetaHolder, + source: SourceIdInfo & GqlMetaHolder, handleError: (error: any) => void = defaultPromiseErrorHandler('createUpdateSourceMetadata'), ): ((...args: OmitFirst>>) => Promise) => (metadataKey, value) => diff --git a/src/modules/source/services/Sources.ts b/src/modules/source/services/Sources.ts new file mode 100644 index 00000000..e118e0b5 --- /dev/null +++ b/src/modules/source/services/Sources.ts @@ -0,0 +1,17 @@ +/* + * 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 { SourceIdInfo } from '@/modules/source/Source.types.ts'; + +export class Sources { + static readonly LOCAL_SOURCE_ID = '0'; + + static isLocalSource(source: SourceIdInfo): boolean { + return source.id === Sources.LOCAL_SOURCE_ID; + } +}