2021-03-26 04:17:02 +04:30
|
|
|
/*
|
|
|
|
|
* Copyright (C) Contributors to the Suwayomi project
|
|
|
|
|
*
|
|
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
2021-02-13 21:12:18 +03:30
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
2023-05-18 13:17:41 +02:00
|
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
|
|
|
*/
|
2021-02-13 21:12:18 +03:30
|
|
|
|
2024-09-06 16:59:27 +02:00
|
|
|
import Chip, { ChipProps } from '@mui/material/Chip';
|
2024-04-14 15:50:12 +02:00
|
|
|
import Tab from '@mui/material/Tab';
|
|
|
|
|
import { styled } from '@mui/material/styles';
|
2024-09-26 15:33:22 +02:00
|
|
|
import { useCallback, useContext, useLayoutEffect, useMemo, useState } from 'react';
|
2021-11-19 16:36:02 +03:30
|
|
|
import { useQueryParam, NumberParam } from 'use-query-params';
|
2023-02-13 20:20:08 +03:30
|
|
|
import { useTranslation } from 'react-i18next';
|
2024-10-05 16:09:34 +02:00
|
|
|
import { requestManager } from '@/lib/requests/requests/RequestManager.ts';
|
|
|
|
|
import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder/EmptyViewAbsoluteCentered.tsx';
|
|
|
|
|
import { LoadingPlaceholder } from '@/modules/core/components/placeholder/LoadingPlaceholder.tsx';
|
|
|
|
|
import { TabPanel } from '@/modules/core/components/tabs/TabPanel.tsx';
|
2023-10-28 00:32:02 +02:00
|
|
|
import { LibraryToolbarMenu } from '@/components/library/LibraryToolbarMenu';
|
|
|
|
|
import { LibraryMangaGrid } from '@/components/library/LibraryMangaGrid';
|
2024-10-05 16:09:34 +02:00
|
|
|
import { AppbarSearch } from '@/modules/core/components/AppbarSearch.tsx';
|
|
|
|
|
import { UpdateChecker } from '@/modules/core/components/UpdateChecker.tsx';
|
2023-10-28 00:32:02 +02:00
|
|
|
import { NavBarContext } from '@/components/context/NavbarContext.tsx';
|
2024-10-05 19:24:45 +02:00
|
|
|
import { useSelectableCollection } from '@/modules/collection/hooks/useSelectableCollection.ts';
|
|
|
|
|
import { SelectableCollectionSelectMode } from '@/modules/collection/components/SelectableCollectionSelectMode.tsx';
|
2023-12-25 21:37:45 +01:00
|
|
|
import { useGetVisibleLibraryMangas } from '@/components/library/useGetVisibleLibraryMangas.ts';
|
2024-10-05 19:24:45 +02:00
|
|
|
import { SelectionFAB } from '@/modules/collection/components/SelectionFAB.tsx';
|
2024-10-05 19:21:26 +02:00
|
|
|
import { MangaActionMenuItems } from '@/modules/manga/components/MangaActionMenuItems.tsx';
|
2024-10-05 16:09:34 +02:00
|
|
|
import { TabsMenu } from '@/modules/core/components/tabs/TabsMenu.tsx';
|
|
|
|
|
import { TabsWrapper } from '@/modules/core/components/tabs/TabsWrapper.tsx';
|
|
|
|
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
2024-07-08 18:02:50 +02:00
|
|
|
import {
|
|
|
|
|
GetCategoriesLibraryQuery,
|
|
|
|
|
GetCategoriesLibraryQueryVariables,
|
|
|
|
|
MangaChapterStatFieldsFragment,
|
|
|
|
|
MangaType,
|
|
|
|
|
} from '@/lib/graphql/generated/graphql.ts';
|
2024-07-01 21:40:27 +02:00
|
|
|
import { GET_CATEGORIES_LIBRARY } from '@/lib/graphql/queries/CategoryQuery.ts';
|
2024-10-05 19:21:26 +02:00
|
|
|
import { Mangas } from '@/modules/manga/services/Mangas.ts';
|
2024-07-08 18:02:50 +02:00
|
|
|
import { MANGA_CHAPTER_STAT_FIELDS } from '@/lib/graphql/fragments/MangaFragments.ts';
|
2024-09-17 03:40:55 +02:00
|
|
|
import { useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext.tsx';
|
|
|
|
|
import { useMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts';
|
|
|
|
|
import { getCategoryMetadata } from '@/lib/metadata/categoryMetadata.ts';
|
2023-06-17 16:55:24 +02:00
|
|
|
|
2023-04-22 11:39:59 +02:00
|
|
|
const TitleWithSizeTag = styled('span')({
|
|
|
|
|
display: 'flex',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
});
|
|
|
|
|
|
2024-09-06 16:59:27 +02:00
|
|
|
const TitleSizeTag = ({ sx, ...props }: ChipProps) => (
|
|
|
|
|
<Chip {...props} size="small" sx={{ ...sx, marginLeft: '5px' }} />
|
2023-04-22 11:39:59 +02:00
|
|
|
);
|
2021-02-13 21:12:18 +03:30
|
|
|
|
2023-10-28 00:32:02 +02:00
|
|
|
export function Library() {
|
2023-02-13 20:20:08 +03:30
|
|
|
const { t } = useTranslation();
|
2023-02-14 16:47:38 +03:30
|
|
|
|
2024-09-17 03:40:55 +02:00
|
|
|
const {
|
|
|
|
|
settings: { showTabSize },
|
|
|
|
|
} = useMetadataServerSettings();
|
|
|
|
|
|
2023-09-06 21:47:13 +02:00
|
|
|
const {
|
|
|
|
|
data: categoriesResponse,
|
|
|
|
|
error: tabsError,
|
|
|
|
|
loading: areCategoriesLoading,
|
2024-04-27 22:29:23 +02:00
|
|
|
refetch: refetchCategories,
|
2024-07-01 21:40:27 +02:00
|
|
|
} = requestManager.useGetCategories<GetCategoriesLibraryQuery, GetCategoriesLibraryQueryVariables>(
|
|
|
|
|
GET_CATEGORIES_LIBRARY,
|
|
|
|
|
{
|
|
|
|
|
notifyOnNetworkStatusChange: true,
|
|
|
|
|
},
|
|
|
|
|
);
|
2023-09-06 21:47:13 +02:00
|
|
|
const tabsData = categoriesResponse?.categories.nodes.filter(
|
|
|
|
|
(category) => category.id !== 0 || (category.id === 0 && category.mangas.totalCount),
|
|
|
|
|
);
|
2022-11-02 10:42:02 +01:00
|
|
|
const tabs = tabsData ?? [];
|
2023-09-01 20:39:30 +02:00
|
|
|
const librarySize = useMemo(
|
|
|
|
|
() => tabs.map((tab) => tab.mangas.totalCount).reduce((prev, curr) => prev + curr, 0),
|
|
|
|
|
[tabs],
|
|
|
|
|
);
|
2022-11-02 10:42:02 +01:00
|
|
|
|
|
|
|
|
const [tabSearchParam, setTabSearchParam] = useQueryParam('tab', NumberParam);
|
|
|
|
|
|
2024-09-17 03:40:55 +02:00
|
|
|
const { setOptions } = useLibraryOptionsContext();
|
|
|
|
|
const activeTab: (typeof tabs)[number] | undefined = tabs.find((tab) => tab.order === tabSearchParam) ?? tabs[0];
|
|
|
|
|
|
|
|
|
|
useLayoutEffect(() => {
|
|
|
|
|
setOptions(getCategoryMetadata(activeTab));
|
|
|
|
|
}, [activeTab]);
|
|
|
|
|
|
2022-11-02 21:48:22 +01:00
|
|
|
const {
|
2023-09-06 21:47:13 +02:00
|
|
|
data: categoryMangaResponse,
|
2022-11-02 21:48:22 +01:00
|
|
|
error: mangaError,
|
2023-09-06 21:47:13 +02:00
|
|
|
loading: mangaLoading,
|
2024-04-27 22:29:23 +02:00
|
|
|
refetch: refetchCategoryMangas,
|
|
|
|
|
} = requestManager.useGetCategoryMangas(activeTab?.id, { skip: !activeTab, notifyOnNetworkStatusChange: true });
|
2023-12-25 21:37:45 +01:00
|
|
|
const categoryMangas = categoryMangaResponse?.mangas.nodes ?? [];
|
2024-09-17 03:40:55 +02:00
|
|
|
const { visibleMangas: mangas, showFilteredOutMessage } = useGetVisibleLibraryMangas(categoryMangas, activeTab);
|
2023-12-25 21:37:45 +01:00
|
|
|
|
2024-04-27 22:29:23 +02:00
|
|
|
const retryFetchCategoryMangas = useCallback(
|
|
|
|
|
() => refetchCategoryMangas().catch(defaultPromiseErrorHandler('Library::refetchCategoryMangas')),
|
|
|
|
|
[refetchCategoryMangas, activeTab],
|
|
|
|
|
);
|
|
|
|
|
|
2024-04-05 02:03:00 +02:00
|
|
|
const mangaIds = useMemo(() => mangas.map((manga) => manga.id), [mangas]);
|
|
|
|
|
|
2023-12-25 21:37:45 +01:00
|
|
|
const [isSelectModeActive, setIsSelectModeActive] = useState(false);
|
|
|
|
|
const {
|
|
|
|
|
areNoItemsForKeySelected: areNoItemsSelected,
|
|
|
|
|
areAllItemsForKeySelected: areAllItemsSelected,
|
|
|
|
|
selectedItemIds,
|
|
|
|
|
handleSelectAll,
|
|
|
|
|
handleSelection,
|
2024-01-21 22:31:43 +01:00
|
|
|
clearSelection,
|
2024-07-08 18:02:50 +02:00
|
|
|
} = useSelectableCollection<MangaType['id'], string>(mangas.length, {
|
2024-04-05 02:03:00 +02:00
|
|
|
itemIds: mangaIds,
|
|
|
|
|
currentKey: activeTab?.id.toString(),
|
|
|
|
|
});
|
2023-12-25 21:37:45 +01:00
|
|
|
|
2024-04-05 02:03:00 +02:00
|
|
|
const handleSelect: typeof handleSelection = (id, selected, selectOptions) => {
|
2023-12-25 21:37:45 +01:00
|
|
|
setIsSelectModeActive(!!(selectedItemIds.length + (selected ? 1 : -1)));
|
2024-04-05 02:03:00 +02:00
|
|
|
handleSelection(id, selected, selectOptions);
|
2023-12-25 21:37:45 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const selectedMangas = useMemo(
|
|
|
|
|
() =>
|
2024-07-08 18:02:50 +02:00
|
|
|
selectedItemIds
|
|
|
|
|
.map((id) =>
|
|
|
|
|
Mangas.getFromCache<MangaChapterStatFieldsFragment>(
|
|
|
|
|
id,
|
|
|
|
|
MANGA_CHAPTER_STAT_FIELDS,
|
|
|
|
|
'MANGA_CHAPTER_STAT_FIELDS',
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.filter((manga) => !!manga),
|
2023-12-30 04:04:47 +01:00
|
|
|
[selectedItemIds.length, mangas],
|
2023-12-25 21:37:45 +01:00
|
|
|
);
|
2022-11-02 10:42:02 +01:00
|
|
|
|
2024-01-15 21:19:35 +01:00
|
|
|
const selectionFab = useMemo(() => {
|
|
|
|
|
if (!isSelectModeActive) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<SelectionFAB selectedItemsCount={selectedItemIds.length} title="manga.title">
|
|
|
|
|
{(handleClose, setHideMenu) => (
|
|
|
|
|
<MangaActionMenuItems
|
|
|
|
|
selectedMangas={selectedMangas}
|
2024-06-13 12:17:02 +02:00
|
|
|
onClose={() => {
|
2024-01-15 21:19:35 +01:00
|
|
|
handleClose();
|
2024-06-13 12:17:02 +02:00
|
|
|
setIsSelectModeActive(false);
|
|
|
|
|
clearSelection();
|
2024-01-15 21:19:35 +01:00
|
|
|
}}
|
|
|
|
|
setHideMenu={setHideMenu}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</SelectionFAB>
|
|
|
|
|
);
|
|
|
|
|
}, [isSelectModeActive, selectedMangas]);
|
|
|
|
|
|
2023-10-28 00:32:02 +02:00
|
|
|
const { setTitle, setAction } = useContext(NavBarContext);
|
2024-09-26 15:33:22 +02:00
|
|
|
useLayoutEffect(() => {
|
2023-04-22 11:39:59 +02:00
|
|
|
const title = t('library.title');
|
|
|
|
|
const navBarTitle = (
|
|
|
|
|
<TitleWithSizeTag>
|
|
|
|
|
{title}
|
2024-09-17 03:40:55 +02:00
|
|
|
{showTabSize && <TitleSizeTag sx={{ color: 'inherit' }} label={librarySize} />}
|
2023-04-22 11:39:59 +02:00
|
|
|
</TitleWithSizeTag>
|
|
|
|
|
);
|
|
|
|
|
setTitle(navBarTitle, title);
|
2022-11-02 10:42:02 +01:00
|
|
|
setAction(
|
2021-10-29 20:11:23 +02:00
|
|
|
<>
|
2023-12-25 21:37:45 +01:00
|
|
|
{!isSelectModeActive && (
|
|
|
|
|
<>
|
|
|
|
|
<AppbarSearch />
|
2024-09-17 03:40:55 +02:00
|
|
|
<LibraryToolbarMenu category={activeTab} />
|
2024-06-26 12:42:03 +02:00
|
|
|
<UpdateChecker categoryId={activeTab?.id} />
|
2023-12-25 21:37:45 +01:00
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
<SelectableCollectionSelectMode
|
|
|
|
|
isActive={isSelectModeActive}
|
|
|
|
|
areAllItemsSelected={areAllItemsSelected}
|
|
|
|
|
areNoItemsSelected={areNoItemsSelected}
|
|
|
|
|
onSelectAll={(selectAll) =>
|
|
|
|
|
handleSelectAll(selectAll, [...new Set(mangas.map((manga) => manga.id))])
|
|
|
|
|
}
|
|
|
|
|
onModeChange={(checked) => {
|
|
|
|
|
setIsSelectModeActive(checked);
|
|
|
|
|
|
|
|
|
|
if (checked) {
|
|
|
|
|
handleSelectAll(true, [...new Set(mangas.map((manga) => manga.id))]);
|
|
|
|
|
} else {
|
|
|
|
|
tabs.forEach((tab) => handleSelectAll(false, [], tab.id.toString()));
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
/>
|
2021-10-29 20:11:23 +02:00
|
|
|
</>,
|
|
|
|
|
);
|
2022-11-09 18:09:15 +01:00
|
|
|
return () => {
|
|
|
|
|
setTitle('');
|
2023-03-11 18:05:58 +01:00
|
|
|
setAction(null);
|
2022-11-09 18:09:15 +01:00
|
|
|
};
|
2023-12-25 21:37:45 +01:00
|
|
|
}, [
|
|
|
|
|
t,
|
|
|
|
|
librarySize,
|
|
|
|
|
areCategoriesLoading,
|
|
|
|
|
isSelectModeActive,
|
|
|
|
|
areNoItemsSelected,
|
|
|
|
|
areAllItemsSelected,
|
|
|
|
|
selectedItemIds.length,
|
|
|
|
|
mangas.length,
|
2024-06-26 12:42:03 +02:00
|
|
|
activeTab,
|
2024-09-17 03:40:55 +02:00
|
|
|
showTabSize,
|
2023-12-25 21:37:45 +01:00
|
|
|
]);
|
2021-03-09 16:44:09 +03:30
|
|
|
|
2021-02-21 04:27:41 +03:30
|
|
|
const handleTabChange = (newTab: number) => {
|
2023-09-06 21:47:13 +02:00
|
|
|
setTabSearchParam(newTab);
|
2021-02-21 04:27:41 +03:30
|
|
|
};
|
|
|
|
|
|
2022-11-02 10:42:02 +01:00
|
|
|
if (tabsError != null) {
|
2023-02-14 16:28:37 +03:30
|
|
|
return (
|
2024-04-29 15:29:33 +02:00
|
|
|
<EmptyViewAbsoluteCentered
|
2023-03-23 13:59:32 +01:00
|
|
|
message={t('category.error.label.request_failure')}
|
2024-04-27 22:29:23 +02:00
|
|
|
messageExtra={tabsError.message}
|
|
|
|
|
retry={() => refetchCategories().catch(defaultPromiseErrorHandler('Library::refetchCategories'))}
|
2023-02-14 16:28:37 +03:30
|
|
|
/>
|
|
|
|
|
);
|
2022-11-02 10:42:02 +01:00
|
|
|
}
|
2021-08-29 20:47:57 +04:30
|
|
|
|
2023-06-05 01:24:56 +02:00
|
|
|
if (areCategoriesLoading) {
|
2021-10-24 22:56:10 +03:30
|
|
|
return <LoadingPlaceholder />;
|
2021-08-29 20:47:57 +04:30
|
|
|
}
|
2021-08-21 03:46:01 +04:30
|
|
|
|
|
|
|
|
if (tabs.length === 0) {
|
2024-04-29 15:29:33 +02:00
|
|
|
return <EmptyViewAbsoluteCentered message={t('library.error.label.empty')} />;
|
2021-08-21 03:46:01 +04:30
|
|
|
}
|
2021-03-07 16:27:13 +03:30
|
|
|
|
2022-11-02 10:42:02 +01:00
|
|
|
if (tabs.length === 1) {
|
|
|
|
|
return (
|
2024-01-15 21:19:35 +01:00
|
|
|
<>
|
|
|
|
|
<LibraryMangaGrid
|
|
|
|
|
mangas={mangas}
|
2024-04-27 22:29:23 +02:00
|
|
|
message={mangaError ? t('manga.error.label.request_failure') : t('library.error.label.empty')}
|
|
|
|
|
messageExtra={mangaError?.message}
|
|
|
|
|
isLoading={mangaLoading}
|
2024-01-15 21:19:35 +01:00
|
|
|
selectedMangaIds={selectedItemIds}
|
|
|
|
|
isSelectModeActive={isSelectModeActive}
|
|
|
|
|
handleSelection={handleSelect}
|
2024-04-27 22:29:23 +02:00
|
|
|
showFilteredOutMessage={!mangaError && showFilteredOutMessage}
|
|
|
|
|
retry={mangaError && retryFetchCategoryMangas}
|
2024-01-15 21:19:35 +01:00
|
|
|
/>
|
|
|
|
|
{selectionFab}
|
|
|
|
|
</>
|
2021-02-20 01:23:52 +03:30
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-02 10:42:02 +01:00
|
|
|
return (
|
2024-01-15 21:19:35 +01:00
|
|
|
<TabsWrapper>
|
2024-10-05 20:55:37 +02:00
|
|
|
<TabsMenu value={activeTab.order} onChange={(e, newTab) => handleTabChange(newTab)}>
|
2022-11-02 10:42:02 +01:00
|
|
|
{tabs.map((tab) => (
|
2024-01-15 21:19:35 +01:00
|
|
|
<Tab
|
|
|
|
|
sx={{ display: 'flex' }}
|
|
|
|
|
key={tab.id}
|
|
|
|
|
label={
|
|
|
|
|
<TitleWithSizeTag>
|
|
|
|
|
{tab.name}
|
2024-09-17 03:40:55 +02:00
|
|
|
{showTabSize ? <TitleSizeTag label={tab.mangas.totalCount} /> : null}
|
2024-01-15 21:19:35 +01:00
|
|
|
</TitleWithSizeTag>
|
|
|
|
|
}
|
|
|
|
|
value={tab.order}
|
|
|
|
|
/>
|
2022-11-02 10:42:02 +01:00
|
|
|
))}
|
2024-01-15 21:19:35 +01:00
|
|
|
</TabsMenu>
|
|
|
|
|
{tabs.map((tab) => (
|
|
|
|
|
<TabPanel key={tab.order} index={tab.order} currentIndex={activeTab.order}>
|
2024-04-27 22:29:23 +02:00
|
|
|
{tab === activeTab && (
|
|
|
|
|
<LibraryMangaGrid
|
|
|
|
|
mangas={mangas}
|
|
|
|
|
message={
|
|
|
|
|
mangaError ? t('manga.error.label.request_failure') : t('library.error.label.empty')
|
|
|
|
|
}
|
|
|
|
|
messageExtra={mangaError?.message}
|
|
|
|
|
isLoading={mangaLoading}
|
|
|
|
|
selectedMangaIds={selectedItemIds}
|
|
|
|
|
isSelectModeActive={isSelectModeActive}
|
|
|
|
|
handleSelection={handleSelect}
|
|
|
|
|
showFilteredOutMessage={!mangaError && showFilteredOutMessage}
|
|
|
|
|
retry={mangaError && retryFetchCategoryMangas}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
2024-01-15 21:19:35 +01:00
|
|
|
</TabPanel>
|
|
|
|
|
))}
|
|
|
|
|
{selectionFab}
|
|
|
|
|
</TabsWrapper>
|
2022-11-02 10:42:02 +01:00
|
|
|
);
|
2021-02-13 21:12:18 +03:30
|
|
|
}
|