2021-10-29 20:11:23 +02:00
|
|
|
/*
|
|
|
|
|
* 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/.
|
|
|
|
|
*/
|
|
|
|
|
|
2023-02-12 20:43:41 +05:30
|
|
|
import React, { useEffect, useState } from 'react';
|
|
|
|
|
import MangaGrid from 'components/MangaGrid';
|
2022-03-06 16:45:25 +00:00
|
|
|
import { useLibraryOptionsContext } from 'components/context/LibraryOptionsContext';
|
2022-04-07 12:53:14 +02:00
|
|
|
import { StringParam, useQueryParam } from 'use-query-params';
|
2023-02-12 20:43:41 +05:30
|
|
|
import { useMediaQuery, useTheme } from '@mui/material';
|
2023-02-24 16:40:37 +01:00
|
|
|
import { IMangaCard, LibrarySortMode, NullAndUndefined } from 'typings';
|
2023-03-04 22:21:24 +05:30
|
|
|
import { useSearchSettings } from 'util/searchSettings';
|
2023-03-23 13:59:32 +01:00
|
|
|
import { useTranslation } from 'react-i18next';
|
2021-10-29 20:11:23 +02:00
|
|
|
|
2022-11-24 21:04:30 +01:00
|
|
|
const unreadFilter = (unread: NullAndUndefined<boolean>, { unreadCount }: IMangaCard): boolean => {
|
2021-10-29 20:11:23 +02:00
|
|
|
switch (unread) {
|
|
|
|
|
case true:
|
|
|
|
|
return !!unreadCount && unreadCount >= 1;
|
|
|
|
|
case false:
|
|
|
|
|
return unreadCount === 0;
|
|
|
|
|
default:
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2022-11-24 21:04:30 +01:00
|
|
|
};
|
2021-10-29 20:11:23 +02:00
|
|
|
|
2023-02-08 18:19:26 +01:00
|
|
|
const downloadedFilter = (downloaded: NullAndUndefined<boolean>, { downloadCount }: IMangaCard): boolean => {
|
2021-11-07 15:59:48 +05:30
|
|
|
switch (downloaded) {
|
|
|
|
|
case true:
|
|
|
|
|
return !!downloadCount && downloadCount >= 1;
|
|
|
|
|
case false:
|
|
|
|
|
return downloadCount === 0;
|
|
|
|
|
default:
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2022-11-24 21:04:30 +01:00
|
|
|
};
|
2021-11-07 15:59:48 +05:30
|
|
|
|
2022-11-24 21:04:30 +01:00
|
|
|
const queryFilter = (query: NullAndUndefined<string>, { title }: IMangaCard): boolean => {
|
2021-10-31 12:45:34 +01:00
|
|
|
if (!query) return true;
|
|
|
|
|
return title.toLowerCase().includes(query.toLowerCase());
|
2022-11-24 21:04:30 +01:00
|
|
|
};
|
2021-10-31 12:45:34 +01:00
|
|
|
|
2023-02-16 11:26:09 +05:30
|
|
|
const queryGenreFilter = (query: NullAndUndefined<string>, { genre }: IMangaCard): boolean => {
|
|
|
|
|
if (!query) return true;
|
|
|
|
|
const queries = query.split(',').map((str) => str.toLowerCase().trim());
|
|
|
|
|
return queries.every((element) => genre.map((el) => el.toLowerCase()).includes(element));
|
|
|
|
|
};
|
|
|
|
|
|
2022-11-24 21:04:30 +01:00
|
|
|
const filterManga = (
|
2023-03-01 19:38:12 +01:00
|
|
|
mangas: IMangaCard[],
|
2022-11-24 21:04:30 +01:00
|
|
|
query: NullAndUndefined<string>,
|
|
|
|
|
unread: NullAndUndefined<boolean>,
|
|
|
|
|
downloaded: NullAndUndefined<boolean>,
|
2023-03-04 22:21:24 +05:30
|
|
|
ignoreFilters: boolean,
|
2023-03-01 19:38:12 +01:00
|
|
|
): IMangaCard[] =>
|
2023-03-11 19:12:43 +01:00
|
|
|
mangas.filter((manga) => {
|
|
|
|
|
const ignoreFiltersWhileSearching = ignoreFilters && query?.length;
|
|
|
|
|
const matchesSearch = queryFilter(query, manga) || queryGenreFilter(query, manga);
|
|
|
|
|
const matchesFilters =
|
|
|
|
|
ignoreFiltersWhileSearching || (downloadedFilter(downloaded, manga) && unreadFilter(unread, manga));
|
|
|
|
|
|
|
|
|
|
return matchesSearch && matchesFilters;
|
|
|
|
|
});
|
2022-11-24 21:04:30 +01:00
|
|
|
|
|
|
|
|
const sortByUnread = (a: IMangaCard, b: IMangaCard): number =>
|
|
|
|
|
// eslint-disable-next-line implicit-arrow-linebreak
|
|
|
|
|
(a.unreadCount ?? 0) - (b.unreadCount ?? 0);
|
|
|
|
|
|
|
|
|
|
const sortByTitle = (a: IMangaCard, b: IMangaCard): number => a.title.localeCompare(b.title);
|
|
|
|
|
|
2023-03-04 22:21:39 +05:30
|
|
|
const sortByDateAdded = (a: IMangaCard, b: IMangaCard): number => a.inLibraryAt - b.inLibraryAt;
|
2022-04-07 12:53:14 +02:00
|
|
|
|
2023-03-05 12:26:50 +05:30
|
|
|
const sortByLastRead = (a: IMangaCard, b: IMangaCard): number => b.lastReadAt - a.lastReadAt;
|
|
|
|
|
|
2022-11-24 21:04:30 +01:00
|
|
|
const sortManga = (
|
|
|
|
|
manga: IMangaCard[],
|
|
|
|
|
sort: NullAndUndefined<LibrarySortMode>,
|
|
|
|
|
desc: NullAndUndefined<boolean>,
|
|
|
|
|
): IMangaCard[] => {
|
|
|
|
|
const result = [...manga];
|
2021-10-29 20:11:23 +02:00
|
|
|
|
2022-11-24 21:04:30 +01:00
|
|
|
switch (sort) {
|
2023-02-06 10:06:33 +01:00
|
|
|
case 'sortAlph':
|
|
|
|
|
result.sort(sortByTitle);
|
|
|
|
|
break;
|
2023-03-04 22:21:39 +05:30
|
|
|
case 'sortDateAdded':
|
|
|
|
|
result.sort(sortByDateAdded);
|
2023-02-06 10:06:33 +01:00
|
|
|
break;
|
|
|
|
|
case 'sortToRead':
|
|
|
|
|
result.sort(sortByUnread);
|
|
|
|
|
break;
|
2023-03-05 12:26:50 +05:30
|
|
|
case 'sortLastRead':
|
|
|
|
|
result.sort(sortByLastRead);
|
|
|
|
|
break;
|
2023-02-06 10:06:33 +01:00
|
|
|
default:
|
|
|
|
|
break;
|
2022-11-24 21:04:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (desc === true) {
|
|
|
|
|
result.reverse();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
};
|
|
|
|
|
|
2023-02-12 20:43:41 +05:30
|
|
|
interface LibraryMangaGridProps {
|
|
|
|
|
mangas: IMangaCard[];
|
|
|
|
|
isLoading: boolean;
|
|
|
|
|
message?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const LibraryMangaGrid: React.FC<LibraryMangaGridProps & { lastLibraryUpdate: number }> = ({
|
2023-02-06 10:06:33 +01:00
|
|
|
mangas,
|
|
|
|
|
isLoading,
|
|
|
|
|
message,
|
|
|
|
|
lastLibraryUpdate,
|
2022-11-24 21:04:30 +01:00
|
|
|
}) => {
|
2023-03-23 13:59:32 +01:00
|
|
|
const { t } = useTranslation();
|
|
|
|
|
|
2022-04-07 12:53:14 +02:00
|
|
|
const [query] = useQueryParam('query', StringParam);
|
2022-11-24 09:41:03 +01:00
|
|
|
const { options } = useLibraryOptionsContext();
|
|
|
|
|
const { unread, downloaded } = options;
|
2023-02-12 20:43:41 +05:30
|
|
|
const [filteredPaginatedMangas, setFilteredPaginatedMangas] = useState<IMangaCard[]>([]);
|
|
|
|
|
const [sortedManga, setSortedManga] = useState<IMangaCard[]>([]);
|
|
|
|
|
const [filteredManga, setFilteredManga] = useState<IMangaCard[]>([]);
|
|
|
|
|
const totalPages = (mangas ?? []).length / 10;
|
|
|
|
|
const theme = useTheme();
|
2023-03-16 18:00:02 +01:00
|
|
|
const isLargeScreen = useMediaQuery(theme.breakpoints.up('sm'), { noSsr: true });
|
2023-02-12 20:43:41 +05:30
|
|
|
const defaultPageNumber = isLargeScreen ? 4 : 1;
|
|
|
|
|
const [lastPageNum, setLastPageNum] = useState<number>(defaultPageNumber);
|
2023-03-04 22:21:24 +05:30
|
|
|
const { settings } = useSearchSettings();
|
|
|
|
|
|
2023-02-12 20:43:41 +05:30
|
|
|
useEffect(() => {
|
2023-03-04 22:21:24 +05:30
|
|
|
setFilteredManga(filterManga(mangas, query, unread, downloaded, settings.ignoreFilters));
|
2023-02-12 20:43:41 +05:30
|
|
|
setLastPageNum(defaultPageNumber);
|
|
|
|
|
window.scrollTo(0, 0);
|
2023-03-04 22:21:24 +05:30
|
|
|
}, [mangas, query, unread, downloaded, settings.ignoreFilters]);
|
2023-02-12 20:43:41 +05:30
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
setSortedManga(sortManga(filteredManga, options.sorts, options.sortDesc));
|
|
|
|
|
}, [filteredManga, lastLibraryUpdate, options.sorts, options.sortDesc]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
setFilteredPaginatedMangas((sortedManga ?? []).slice(0, lastPageNum * 10));
|
|
|
|
|
}, [lastPageNum, sortedManga]);
|
2022-11-24 21:04:30 +01:00
|
|
|
|
2023-02-06 10:06:33 +01:00
|
|
|
const showFilteredOutMessage =
|
2023-02-08 18:19:26 +01:00
|
|
|
(unread != null || downloaded != null || query) && filteredManga.length === 0 && mangas.length > 0;
|
2021-10-29 20:11:23 +02:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<MangaGrid
|
2023-02-12 20:43:41 +05:30
|
|
|
mangas={filteredPaginatedMangas}
|
2021-10-29 20:11:23 +02:00
|
|
|
isLoading={isLoading}
|
2023-02-12 20:43:41 +05:30
|
|
|
hasNextPage={lastPageNum < totalPages}
|
2021-10-29 20:11:23 +02:00
|
|
|
lastPageNum={lastPageNum}
|
|
|
|
|
setLastPageNum={setLastPageNum}
|
2023-03-23 13:59:32 +01:00
|
|
|
message={showFilteredOutMessage ? t('library.error.label.no_matches') : message}
|
2022-03-06 16:45:25 +00:00
|
|
|
gridLayout={options.gridLayout}
|
2021-10-29 20:11:23 +02:00
|
|
|
/>
|
|
|
|
|
);
|
2022-11-24 21:04:30 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default LibraryMangaGrid;
|