diff --git a/public/locales/en.json b/public/locales/en.json index d6a84485..0ff581bd 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -429,6 +429,7 @@ "horizontal": "Horizontal", "info": "Information", "large": "Large", + "last_used": "Last used", "left": "Left", "light": "Light", "links": "Links", diff --git a/src/modules/browse/Browse.types.ts b/src/modules/browse/Browse.types.ts index cb814a2c..3c0cea87 100644 --- a/src/modules/browse/Browse.types.ts +++ b/src/modules/browse/Browse.types.ts @@ -6,8 +6,11 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import { SourceIdInfo } from '@/modules/source/Source.types.ts'; + export type MetadataBrowseSettings = { hideLibraryEntries: boolean; extensionLanguages: string[]; showNsfw: boolean; + lastUsedSourceId: SourceIdInfo['id'] | null; }; diff --git a/src/modules/core/utils/Languages.ts b/src/modules/core/utils/Languages.ts index 0568e415..2438e283 100644 --- a/src/modules/core/utils/Languages.ts +++ b/src/modules/core/utils/Languages.ts @@ -15,6 +15,7 @@ export enum DefaultLanguage { OTHER = 'other', LOCAL_SOURCE = 'localsourcelang', PINNED = 'pinned', + LAST_USED_SOURCE = 'last_used_source', } const DEFAULT_LANGUAGE_TO_TRANSLATION: Record = { @@ -22,6 +23,7 @@ const DEFAULT_LANGUAGE_TO_TRANSLATION: Record = [DefaultLanguage.OTHER]: 'extension.language.other', [DefaultLanguage.LOCAL_SOURCE]: 'extension.language.other', [DefaultLanguage.PINNED]: 'global.label.pinned', + [DefaultLanguage.LAST_USED_SOURCE]: 'global.label.last_used', }; type LanguageObject = ISOLanguage & { orgCode: string; isoCode: string }; diff --git a/src/modules/extension/Extensions.constants.ts b/src/modules/extension/Extensions.constants.ts index d8e61390..6cdefea3 100644 --- a/src/modules/extension/Extensions.constants.ts +++ b/src/modules/extension/Extensions.constants.ts @@ -54,4 +54,5 @@ export const extensionLanguageToTranslationKey: { [state in ExtensionGroupState [DefaultLanguage.OTHER]: 'extension.language.other', [DefaultLanguage.LOCAL_SOURCE]: 'extension.language.other', [DefaultLanguage.PINNED]: 'global.label.pinned', + [DefaultLanguage.LAST_USED_SOURCE]: 'global.label.last_used', }; diff --git a/src/modules/extension/Extensions.utils.ts b/src/modules/extension/Extensions.utils.ts index f9b54465..dead8ab3 100644 --- a/src/modules/extension/Extensions.utils.ts +++ b/src/modules/extension/Extensions.utils.ts @@ -49,9 +49,13 @@ export const isExtensionState = (value: string): boolean => export const isExtensionStateOrLanguage = (languageCode: string): boolean => isExtensionState(languageCode) || - [DefaultLanguage.ALL, DefaultLanguage.OTHER, DefaultLanguage.LOCAL_SOURCE, DefaultLanguage.PINNED].includes( - languageCode as DefaultLanguage, - ); + [ + DefaultLanguage.ALL, + DefaultLanguage.OTHER, + DefaultLanguage.LOCAL_SOURCE, + DefaultLanguage.PINNED, + DefaultLanguage.LAST_USED_SOURCE, + ].includes(languageCode as DefaultLanguage); export const translateExtensionLanguage = (languageCode: string): string => isExtensionStateOrLanguage(languageCode) diff --git a/src/modules/metadata/Metadata.constants.ts b/src/modules/metadata/Metadata.constants.ts index 23869aab..6ae06960 100644 --- a/src/modules/metadata/Metadata.constants.ts +++ b/src/modules/metadata/Metadata.constants.ts @@ -96,6 +96,7 @@ export const APP_METADATA_KEY_TO_TYPE = { shouldUsePureBlackMode: 'boolean', mangaGridItemWidth: 'number', isPinned: 'boolean', + lastUsedSourceId: 'string', } as const satisfies Record; export const VALID_APP_METADATA_KEYS = Object.keys(APP_METADATA_KEY_TO_TYPE); @@ -147,6 +148,7 @@ export const GLOBAL_METADATA_KEYS: AppMetadataKeys[] = [ 'hideLibraryEntries', 'extensionLanguages', 'showNsfw', + 'lastUsedSourceId', // history 'hideHistory', diff --git a/src/modules/settings/Settings.constants.ts b/src/modules/settings/Settings.constants.ts index 32a53250..7f9088bc 100644 --- a/src/modules/settings/Settings.constants.ts +++ b/src/modules/settings/Settings.constants.ts @@ -44,6 +44,7 @@ export const SERVER_SETTINGS_METADATA_DEFAULT: MetadataServerSettings = { hideLibraryEntries: false, extensionLanguages: getDefaultLanguages(), showNsfw: true, + lastUsedSourceId: null, // history hideHistory: false, diff --git a/src/modules/source/screens/SourceMangas.tsx b/src/modules/source/screens/SourceMangas.tsx index 02e39d5e..eb30fe07 100644 --- a/src/modules/source/screens/SourceMangas.tsx +++ b/src/modules/source/screens/SourceMangas.tsx @@ -35,7 +35,10 @@ import { GetSourceMangasFetchMutation, GetSourceMangasFetchMutationVariables, } from '@/lib/graphql/generated/graphql.ts'; -import { useMetadataServerSettings } from '@/modules/settings/services/ServerSettingsMetadata.ts'; +import { + updateMetadataServerSettings, + useMetadataServerSettings, +} from '@/modules/settings/services/ServerSettingsMetadata.ts'; import { useLocalStorage, useSessionStorage } from '@/modules/core/hooks/useStorage.tsx'; import { AppStorage } from '@/lib/storage/AppStorage.ts'; import { getGridSnapshotKey } from '@/modules/manga/components/MangaGrid.tsx'; @@ -382,6 +385,12 @@ export function SourceMangas() { setFiltersToApply([]); }; + useEffect(() => { + updateMetadataServerSettings('lastUsedSourceId', sourceId).catch( + defaultPromiseErrorHandler('SourceMangas::setLastUsedSourceId'), + ); + }, [sourceId]); + useEffect(() => { if (filteredOutAllItemsOfFetchedPage && hasNextPage && !loading) { loadPage(lastPageNum + 1); diff --git a/src/modules/source/screens/Sources.tsx b/src/modules/source/screens/Sources.tsx index 432626c3..ed911ddc 100644 --- a/src/modules/source/screens/Sources.tsx +++ b/src/modules/source/screens/Sources.tsx @@ -15,7 +15,7 @@ import { useTranslation } from 'react-i18next'; import { CustomTooltip } from '@/modules/core/components/CustomTooltip.tsx'; import { requestManager } from '@/lib/requests/RequestManager.ts'; import { useLocalStorage } from '@/modules/core/hooks/useStorage.tsx'; -import { getDefaultLanguages } from '@/modules/core/utils/Languages.ts'; +import { DefaultLanguage, getDefaultLanguages } from '@/modules/core/utils/Languages.ts'; import { LoadingPlaceholder } from '@/modules/core/components/feedback/LoadingPlaceholder.tsx'; import { SourceCard } from '@/modules/source/components/SourceCard.tsx'; import { LanguageSelect } from '@/modules/core/components/inputs/LanguageSelect.tsx'; @@ -33,7 +33,7 @@ export function Sources() { const [shownLangs, setShownLangs] = useLocalStorage('shownSourceLangs', getDefaultLanguages()); const { - settings: { showNsfw }, + settings: { showNsfw, lastUsedSourceId }, } = useMetadataServerSettings(); const { @@ -47,10 +47,19 @@ export function Sources() { () => SourceService.filter(sources ?? [], { showNsfw, languages: shownLangs, keepLocalSource: true }), [sources, shownLangs], ); - const sourcesByLanguageTuple = useMemo( - () => Object.entries(SourceService.groupByLanguage(filteredSources)), - [filteredSources], - ); + const sourcesByLanguage = useMemo(() => { + const lastUsedSource = SourceService.getLastUsedSource(lastUsedSourceId, filteredSources); + const groupedByLanguageTuple = Object.entries(SourceService.groupByLanguage(filteredSources)); + + if (lastUsedSource) { + return [ + [DefaultLanguage.LAST_USED_SOURCE, [lastUsedSource]], + ...groupedByLanguageTuple, + ] satisfies typeof groupedByLanguageTuple; + } + + return groupedByLanguageTuple; + }, [filteredSources]); const sourceLanguages = useMemo(() => SourceService.getLanguages(sources ?? []), [sources]); const areSourcesFromDifferentRepos = useMemo( @@ -94,10 +103,10 @@ export function Sources() { return ( <> - {sourcesByLanguageTuple.map(([languages, sourcesOfLanguage]) => ( - + {sourcesByLanguage.map(([language, sourcesOfLanguage]) => ( + - {translateExtensionLanguage(languages)} + {translateExtensionLanguage(language)} {sourcesOfLanguage.map((source) => ( diff --git a/src/modules/source/services/Sources.ts b/src/modules/source/services/Sources.ts index 4d8ea28c..a7ec2316 100644 --- a/src/modules/source/services/Sources.ts +++ b/src/modules/source/services/Sources.ts @@ -109,4 +109,11 @@ export class Sources { return sources.some((source) => source.extension.repo !== repo && !Sources.isLocalSource(source)); } + + static getLastUsedSource( + lastUsedSourceId: SourceIdInfo['id'] | null, + sources: Source[], + ): Source | undefined { + return sources.find((source) => source.id === lastUsedSourceId); + } }