diff --git a/public/locales/en.json b/public/locales/en.json index 3934c1ed..7795d945 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -704,6 +704,15 @@ "reload_from_source": "Reload data from source", "status": "Status" }, + "status": { + "cancelled": "Cancelled", + "completed": "Completed", + "finished": "Finished", + "hiatus": "Hiatus", + "licensed": "Licensed", + "ongoing": "Ongoing", + "unknown": "$t(global.label.unknown)" + }, "title_one": "Manga", "title_other": "Manga" }, diff --git a/src/components/context/LibraryOptionsContext.tsx b/src/components/context/LibraryOptionsContext.tsx index 31965149..c2e9d706 100644 --- a/src/components/context/LibraryOptionsContext.tsx +++ b/src/components/context/LibraryOptionsContext.tsx @@ -34,6 +34,7 @@ export const DefaultLibraryOptions: LibraryOptions = { unread: undefined, hasDuplicateChapters: undefined, tracker: {}, + status: {} as LibraryOptions['status'], showTabSize: false, }; diff --git a/src/components/library/LibraryOptionsPanel.tsx b/src/components/library/LibraryOptionsPanel.tsx index 5f7698b5..6e3c7125 100644 --- a/src/components/library/LibraryOptionsPanel.tsx +++ b/src/components/library/LibraryOptionsPanel.tsx @@ -19,8 +19,9 @@ import { GridLayout, useLibraryOptionsContext } from '@/components/context/Libra import { OptionsTabs } from '@/components/molecules/OptionsTabs'; import { requestManager } from '@/lib/requests/RequestManager.ts'; import { Trackers } from '@/lib/data/Trackers.ts'; -import { GetTrackersSettingsQuery } from '@/lib/graphql/generated/graphql.ts'; +import { GetTrackersSettingsQuery, MangaStatus } from '@/lib/graphql/generated/graphql.ts'; import { GET_TRACKERS_SETTINGS } from '@/lib/graphql/queries/TrackerQuery.ts'; +import { statusToTranslationKey } from '@/lib/data/Mangas.ts'; const TITLES: { [key in 'filter' | 'sort' | 'display']: TranslationKey } = { filter: 'global.label.filter', @@ -83,6 +84,19 @@ export const LibraryOptionsPanel: React.FC = ({ open, onClose }) => { checked={options.hasDuplicateChapters} onChange={(c) => handleFilterChange('hasDuplicateChapters', c)} /> + {t('manga.label.status')} + {Object.values(MangaStatus).map((status) => ( + + handleFilterChange('status', { + ...options.status, + [status]: checked, + }) + } + /> + ))} {t('global.filter.label.tracked')} {loggedInTrackers.map((tracker) => ( !isTrackerBound, ); }) - .every((matchesFilter) => matchesFilter); + .every(Boolean); -type TMangaFilterOptions = { - unread: NullAndUndefined; - downloaded: NullAndUndefined; - bookmarked: NullAndUndefined; - hasDuplicateChapters: NullAndUndefined; - tracker: LibraryOptions['tracker']; -}; +type TMangaStatusFilter = Pick; +const statusFilter = (statusFilters: LibraryOptions['status'], manga: TMangaStatusFilter): boolean => + Object.entries(statusFilters) + .map(([status, statusFilterState]) => triStateFilterBoolean(statusFilterState, status === manga.status)) + .every(Boolean); + +type TMangaFilterOptions = Pick< + LibraryOptions, + 'unread' | 'downloaded' | 'bookmarked' | 'hasDuplicateChapters' | 'tracker' | 'status' +>; type TMangaFilter = Pick & - TMangaTrackerFilter; + TMangaTrackerFilter & + TMangaStatusFilter; const filterManga = ( manga: TMangaFilter, - { unread, downloaded, bookmarked, hasDuplicateChapters, tracker }: TMangaFilterOptions, + { unread, downloaded, bookmarked, hasDuplicateChapters, tracker, status }: TMangaFilterOptions, ): boolean => triStateFilterNumber(downloaded, manga.downloadCount) && triStateFilterNumber(unread, manga.unreadCount) && triStateFilterNumber(bookmarked, manga.bookmarkCount) && triStateFilterBoolean(hasDuplicateChapters, manga.hasDuplicateChapters) && - trackerFilter(tracker, manga); + trackerFilter(tracker, manga) && + statusFilter(status, manga); type TMangasFilter = TMangaQueryFilter & TMangaFilter; const filterMangas = ( @@ -183,7 +188,7 @@ export const useGetVisibleLibraryMangas = { const [query] = useQueryParam('query', StringParam); const { options } = useLibraryOptionsContext(); - const { unread, downloaded, bookmarked, tracker, hasDuplicateChapters } = options; + const { unread, downloaded, bookmarked, tracker, hasDuplicateChapters, status } = options; const { settings } = useMetadataServerSettings(); const filteredMangas = useMemo( @@ -194,6 +199,7 @@ export const useGetVisibleLibraryMangas = = { + [MangaStatus.Cancelled]: 'manga.status.cancelled', + [MangaStatus.Completed]: 'manga.status.completed', + [MangaStatus.Licensed]: 'manga.status.licensed', + [MangaStatus.Ongoing]: 'manga.status.ongoing', + [MangaStatus.OnHiatus]: 'manga.status.hiatus', + [MangaStatus.PublishingFinished]: 'manga.status.finished', + [MangaStatus.Unknown]: 'manga.status.unknown', +}; + export const actionToTranslationKey: { [key in MangaAction]: { action: { diff --git a/src/lib/graphql/fragments/MangaFragments.ts b/src/lib/graphql/fragments/MangaFragments.ts index ad7d60cb..74db6948 100644 --- a/src/lib/graphql/fragments/MangaFragments.ts +++ b/src/lib/graphql/fragments/MangaFragments.ts @@ -96,6 +96,7 @@ export const MANGA_LIBRARY_FIELDS = gql` genre lastFetchedAt inLibraryAt + status artist author diff --git a/src/lib/graphql/generated/graphql.ts b/src/lib/graphql/generated/graphql.ts index e9a60dd7..c81c09dc 100644 --- a/src/lib/graphql/generated/graphql.ts +++ b/src/lib/graphql/generated/graphql.ts @@ -2751,7 +2751,7 @@ export type MangaChapterNodeFieldsFragment = { __typename?: 'MangaType', firstUn export type MangaReaderFieldsFragment = { __typename?: 'MangaType', id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, meta: Array<{ __typename?: 'MangaMetaType', key: string, value: string }>, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number } }; -export type MangaLibraryFieldsFragment = { __typename?: 'MangaType', genre: Array, lastFetchedAt?: string | null, inLibraryAt: string, artist?: string | null, author?: string | null, description?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, source?: { __typename?: 'SourceType', id: string, displayName: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null }; +export type MangaLibraryFieldsFragment = { __typename?: 'MangaType', genre: Array, lastFetchedAt?: string | null, inLibraryAt: string, status: MangaStatus, artist?: string | null, author?: string | null, description?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, source?: { __typename?: 'SourceType', id: string, displayName: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null }; export type MangaScreenFieldsFragment = { __typename?: 'MangaType', artist?: string | null, author?: string | null, description?: string | null, status: MangaStatus, realUrl?: string | null, sourceId: string, genre: Array, lastFetchedAt?: string | null, inLibraryAt: string, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, source?: { __typename?: 'SourceType', id: string, displayName: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null }; @@ -3274,7 +3274,7 @@ export type GetCategoryMangasQueryVariables = Exact<{ }>; -export type GetCategoryMangasQuery = { __typename?: 'Query', category: { __typename?: 'CategoryType', id: number, mangas: { __typename?: 'MangaNodeList', totalCount: number, nodes: Array<{ __typename?: 'MangaType', genre: Array, lastFetchedAt?: string | null, inLibraryAt: string, artist?: string | null, author?: string | null, description?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, source?: { __typename?: 'SourceType', id: string, displayName: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null }>, pageInfo: { __typename?: 'PageInfo', endCursor?: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null } } } }; +export type GetCategoryMangasQuery = { __typename?: 'Query', category: { __typename?: 'CategoryType', id: number, mangas: { __typename?: 'MangaNodeList', totalCount: number, nodes: Array<{ __typename?: 'MangaType', genre: Array, lastFetchedAt?: string | null, inLibraryAt: string, status: MangaStatus, artist?: string | null, author?: string | null, description?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, source?: { __typename?: 'SourceType', id: string, displayName: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null }>, pageInfo: { __typename?: 'PageInfo', endCursor?: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null } } } }; export type GetChaptersReaderQueryVariables = Exact<{ after?: InputMaybe; @@ -3432,7 +3432,7 @@ export type GetMangasLibraryQueryVariables = Exact<{ }>; -export type GetMangasLibraryQuery = { __typename?: 'Query', mangas: { __typename?: 'MangaNodeList', totalCount: number, nodes: Array<{ __typename?: 'MangaType', genre: Array, lastFetchedAt?: string | null, inLibraryAt: string, artist?: string | null, author?: string | null, description?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, source?: { __typename?: 'SourceType', id: string, displayName: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null }>, pageInfo: { __typename?: 'PageInfo', endCursor?: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null } } }; +export type GetMangasLibraryQuery = { __typename?: 'Query', mangas: { __typename?: 'MangaNodeList', totalCount: number, nodes: Array<{ __typename?: 'MangaType', genre: Array, lastFetchedAt?: string | null, inLibraryAt: string, status: MangaStatus, artist?: string | null, author?: string | null, description?: string | null, id: number, title: string, thumbnailUrl?: string | null, thumbnailUrlLastFetched?: string | null, inLibrary: boolean, initialized: boolean, sourceId: string, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, source?: { __typename?: 'SourceType', id: string, displayName: string } | null, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> }, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null }>, pageInfo: { __typename?: 'PageInfo', endCursor?: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null } } }; export type GetMangasDuplicatesQueryVariables = Exact<{ after?: InputMaybe; diff --git a/src/typings.ts b/src/typings.ts index 35926a5d..361d1715 100644 --- a/src/typings.ts +++ b/src/typings.ts @@ -16,6 +16,7 @@ import { GetSourceBrowseQuery, GetSourceSettingsQuery, MangaReaderFieldsFragment, + MangaStatus, MetaType, SourcePreferenceChangeInput, TrackerType, @@ -281,6 +282,7 @@ export interface LibraryOptions { sortDesc: NullAndUndefined; showTabSize: boolean; tracker: Record>; + status: Record>; } export type ServerSettings = GetServerSettingsQuery['settings'];