/* * 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 type { MessageDescriptor } from '@lingui/core'; import FormLabel from '@mui/material/FormLabel'; import RadioGroup from '@mui/material/RadioGroup'; import { useLingui } from '@lingui/react/macro'; import { msg } from '@lingui/core/macro'; import type { ReactNode } from 'react'; import { useMemo } from 'react'; import uniqBy from 'lodash/fp/uniqBy'; import { CheckboxInput } from '@/base/components/inputs/CheckboxInput.tsx'; import { RadioInput } from '@/base/components/inputs/RadioInput.tsx'; import { SortRadioInput, SortRadioInputRandom } from '@/base/components/inputs/SortRadioInput.tsx'; import { ThreeStateCheckboxInput } from '@/base/components/inputs/ThreeStateCheckboxInput.tsx'; import { OptionsTabs } from '@/base/components/modals/OptionsTabs.tsx'; import { requestManager } from '@/lib/requests/RequestManager.ts'; import { Trackers } from '@/features/tracker/services/Trackers.ts'; import type { GetTrackersSettingsQuery } from '@/lib/graphql/generated/graphql.ts'; import { MangaStatus } from '@/lib/graphql/generated/graphql-base.types.ts'; import { GET_TRACKERS_SETTINGS } from '@/lib/graphql/tracker/TrackerQuery.ts'; import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts'; import { createUpdateCategoryMetadata, useGetCategoryMetadata } from '@/features/category/services/CategoryMetadata.ts'; import { makeToast } from '@/base/utils/Toast.ts'; import { createUpdateMetadataServerSettings, updateMetadataServerSettings, useMetadataServerSettings, } from '@/features/settings/services/ServerSettingsMetadata.ts'; import type { LibrarySortMode } from '@/features/library/Library.types.ts'; import type { CategoryMetadataInfo } from '@/features/category/Category.types.ts'; import { MANGA_STATUS_TO_TRANSLATION } from '@/features/manga/Manga.constants.ts'; import { GridLayout } from '@/base/Base.types'; import { getErrorMessage } from '@/lib/HelperFunctions.ts'; import { Collapsable } from '@/base/components/Collapsable.tsx'; const TITLES: { [key in 'filter' | 'sort' | 'display']: MessageDescriptor } = { filter: msg`Filter`, sort: msg`Sort`, display: msg`Display`, }; const SORT_OPTIONS: [LibrarySortMode, MessageDescriptor][] = [ ['unreadChapters', msg`Unread chapters`], ['totalChapters', msg`Total chapters`], ['alphabetically', msg`A-Z`], ['dateAdded', msg`Recently added`], ['lastRead', msg`Recently read`], ['latestFetchedChapter', msg`Latest fetched chapter`], ['latestUploadedChapter', msg`Latest uploaded chapter`], ['random', msg`Random`], ]; const CollapsableFilter = ({ title, items, isActive }: { title: string; items: ReactNode[]; isActive: boolean }) => ( ); export const LibraryOptionsPanel = ({ category, open, onClose, }: { category: CategoryMetadataInfo; open: boolean; onClose: () => void; }) => { const { t } = useLingui(); const trackerList = requestManager.useGetTrackerList(GET_TRACKERS_SETTINGS); const loggedInTrackers = Trackers.getLoggedIn(trackerList.data?.trackers.nodes ?? STABLE_EMPTY_ARRAY); const migratableSourcesResult = requestManager.useGetMigratableSources(); const librarySources = useMemo(() => { const sources = migratableSourcesResult.data?.mangas.nodes .map(({ source }) => source) .filter((source) => source != null); const uniqueSources = uniqBy('id', sources); return uniqueSources.toSorted((a, b) => a.displayName.localeCompare(b.displayName)); }, [migratableSourcesResult.data?.mangas.nodes]); const categoryLibraryOptions = useGetCategoryMetadata(category); const updateCategoryLibraryOptions = createUpdateCategoryMetadata(category, (e) => makeToast(t`Failed to save changes`, 'error', getErrorMessage(e)), ); const { settings: { showTabSize, showContinueReadingButton, showDownloadBadge, showUnreadBadge, gridLayout }, } = useMetadataServerSettings(); const setSettingValue = createUpdateMetadataServerSettings((e) => makeToast(t`Could not save the default search settings to the server`, 'error', getErrorMessage(e)), ); const isStatusFilterActive = Object.values(MangaStatus).some( (status) => categoryLibraryOptions.hasStatus[status] != null, ); const isTrackerFilterActive = loggedInTrackers.some( (tracker) => categoryLibraryOptions.hasTrackerBinding[tracker.id] != null, ); const isSourceFilterActive = librarySources.some((source) => categoryLibraryOptions.hasSource[source.id] != null); return ( open={open} onClose={onClose} tabs={['filter', 'sort', 'display']} tabTitle={(key) => t(TITLES[key])} tabContent={(key) => { if (key === 'filter') { return ( <> updateCategoryLibraryOptions('hasUnreadChapters', c)} /> updateCategoryLibraryOptions('hasReadChapters', c)} /> updateCategoryLibraryOptions('hasDownloadedChapters', c)} /> updateCategoryLibraryOptions('hasBookmarkedChapters', c)} /> updateCategoryLibraryOptions('hasDuplicateChapters', c)} /> ( updateCategoryLibraryOptions('hasStatus', { ...categoryLibraryOptions.hasStatus, [status]: checked, }) } /> ))} isActive={isStatusFilterActive} /> {!!loggedInTrackers.length && ( ( updateCategoryLibraryOptions('hasTrackerBinding', { ...categoryLibraryOptions.hasTrackerBinding, [tracker.id]: checked, }) } /> ))} isActive={isTrackerFilterActive} /> )} {!!librarySources.length && ( ( updateCategoryLibraryOptions('hasSource', { ...categoryLibraryOptions.hasSource, [source.id]: checked, }) } /> ))} isActive={isSourceFilterActive} /> )} ); } if (key === 'sort') { return SORT_OPTIONS.map(([mode, label]) => mode === 'random' ? ( mode !== categoryLibraryOptions.sortBy ? updateCategoryLibraryOptions('sortBy', mode) : updateCategoryLibraryOptions('sortDesc', !categoryLibraryOptions.sortDesc) } /> ) : ( mode !== categoryLibraryOptions.sortBy ? updateCategoryLibraryOptions('sortBy', mode) : updateCategoryLibraryOptions('sortDesc', !categoryLibraryOptions.sortDesc) } /> ), ); } if (key === 'display') { return ( <> {t`Display mode`} updateMetadataServerSettings('gridLayout', Number(e.target.value))} value={gridLayout} > {t`Badges`} updateMetadataServerSettings('showUnreadBadge', !showUnreadBadge)} /> updateMetadataServerSettings('showDownloadBadge', !showDownloadBadge)} /> {t`Tabs`} setSettingValue('showTabSize', !showTabSize)} /> {t`Other`} updateMetadataServerSettings( 'showContinueReadingButton', !showContinueReadingButton, ) } /> ); } return null; }} /> ); };