Feature/library manga actions (#506)

* Extend "IMangaGridProps" from "DefaultGridProps"

* Move library manga filtering into hook

* Add logic to select mangas

* Add manga actions

* Remove cache only policy for category mangas

Unclear why this was added

* Prevent SelectionFAB from being hidden by the mobile footer

On e.g. the library page, the footer is visible and thus, the fab was not completely visible

* Update categories of manga only after clicking "OK"

Previously, selecting a category resulted in an immediate mutation.
To be able to reuse the "CategorySelect" component to change the categories of multiple mangas at once, this behaviour is not suited.

* Add action to change categories of multiple mangas

* Cancel selection mode after removal from library

The mangas would still be selected and other action could get performed. However, this should only be possible for mangas that are in the library

* Remove unintentionally added console log

Accidentally added with 980da657d9
This commit is contained in:
schroda
2023-12-25 21:37:45 +01:00
committed by GitHub
parent 980da657d9
commit 446deeae06
23 changed files with 1760 additions and 427 deletions

View File

@@ -10,13 +10,15 @@ import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 're
import Grid, { GridTypeMap } from '@mui/material/Grid';
import { Box, Typography } from '@mui/material';
import { GridItemProps, GridStateSnapshot, VirtuosoGrid } from 'react-virtuoso';
import { useNavigate, useLocation } from 'react-router-dom';
import { useLocation, useNavigate } from 'react-router-dom';
import { EmptyView } from '@/components/util/EmptyView';
import { LoadingPlaceholder } from '@/components/util/LoadingPlaceholder';
import { MangaCard } from '@/components/MangaCard';
import { GridLayout } from '@/components/context/LibraryOptionsContext';
import { useLocalStorage } from '@/util/useLocalStorage';
import { TPartialManga } from '@/typings.ts';
import { TManga, TPartialManga } from '@/typings.ts';
import { SelectableCollectionReturnType } from '@/components/collection/useSelectableCollection.ts';
import { DEFAULT_FULL_FAB_HEIGHT } from '@/components/util/StyledFab.tsx';
const GridContainer = React.forwardRef<HTMLDivElement, GridTypeMap['props']>(({ children, ...props }, ref) => (
<Grid {...props} ref={ref} container sx={{ paddingLeft: '5px', paddingRight: '13px' }}>
@@ -40,8 +42,22 @@ const GridItemContainerWithDimension = (
);
};
const createMangaCard = (manga: TPartialManga, gridLayout?: GridLayout, inLibraryIndicator?: boolean) => (
<MangaCard key={manga.id} manga={manga} gridLayout={gridLayout} inLibraryIndicator={inLibraryIndicator} />
const createMangaCard = (
manga: TPartialManga,
gridLayout?: GridLayout,
inLibraryIndicator?: boolean,
isSelectModeActive: boolean = false,
selectedMangaIds?: TManga['id'][],
handleSelection?: DefaultGridProps['handleSelection'],
) => (
<MangaCard
key={manga.id}
manga={manga}
gridLayout={gridLayout}
inLibraryIndicator={inLibraryIndicator}
selected={isSelectModeActive ? selectedMangaIds?.includes(manga.id) : null}
handleSelection={handleSelection}
/>
);
type DefaultGridProps = {
@@ -50,9 +66,21 @@ type DefaultGridProps = {
inLibraryIndicator?: boolean;
GridItemContainer: (props: GridTypeMap['props'] & Partial<GridItemProps>) => JSX.Element;
gridLayout?: GridLayout;
isSelectModeActive?: boolean;
selectedMangaIds?: Required<TManga['id']>[];
handleSelection?: SelectableCollectionReturnType<TManga['id']>['handleSelection'];
};
const HorizontalGrid = ({ isLoading, mangas, inLibraryIndicator, GridItemContainer, gridLayout }: DefaultGridProps) => (
const HorizontalGrid = ({
isLoading,
mangas,
inLibraryIndicator,
GridItemContainer,
gridLayout,
isSelectModeActive,
selectedMangaIds,
handleSelection,
}: DefaultGridProps) => (
<Grid
container
spacing={1}
@@ -70,7 +98,14 @@ const HorizontalGrid = ({ isLoading, mangas, inLibraryIndicator, GridItemContain
) : (
mangas.map((manga) => (
<GridItemContainer key={manga.id}>
{createMangaCard(manga, gridLayout, inLibraryIndicator)}
{createMangaCard(
manga,
gridLayout,
inLibraryIndicator,
isSelectModeActive,
selectedMangaIds,
handleSelection,
)}
</GridItemContainer>
))
)}
@@ -85,6 +120,9 @@ const VerticalGrid = ({
gridLayout,
hasNextPage,
loadMore,
isSelectModeActive,
selectedMangaIds,
handleSelection,
}: DefaultGridProps & {
hasNextPage: boolean;
loadMore: () => void;
@@ -125,26 +163,38 @@ const VerticalGrid = ({
restoreStateFrom={snapshot}
stateChanged={persistGridState}
endReached={() => loadMore()}
itemContent={(index) => createMangaCard(mangas[index], gridLayout, inLibraryIndicator)}
itemContent={(index) =>
createMangaCard(
mangas[index],
gridLayout,
inLibraryIndicator,
isSelectModeActive,
selectedMangaIds,
handleSelection,
)
}
/>
{/* render div to prevent UI jumping around when showing/hiding loading placeholder */
/* eslint-disable-next-line no-nested-ternary */}
{isLoading ? <LoadingPlaceholder /> : hasNextPage ? <div style={{ height: '75px' }} /> : null}
{isSelectModeActive && gridLayout === GridLayout.List ? (
<Box sx={{ paddingBottom: DEFAULT_FULL_FAB_HEIGHT }} />
) : // eslint-disable-next-line no-nested-ternary
isLoading ? (
<LoadingPlaceholder />
) : hasNextPage ? (
<div style={{ height: '75px' }} />
) : null}
</>
);
};
export interface IMangaGridProps {
mangas: TPartialManga[];
isLoading: boolean;
export interface IMangaGridProps extends Omit<DefaultGridProps, 'GridItemContainer'> {
message?: string;
messageExtra?: JSX.Element;
hasNextPage: boolean;
loadMore: () => void;
gridLayout?: GridLayout;
horizontal?: boolean | undefined;
noFaces?: boolean | undefined;
inLibraryIndicator?: boolean;
}
export const MangaGrid: React.FC<IMangaGridProps> = (props) => {
@@ -159,6 +209,9 @@ export const MangaGrid: React.FC<IMangaGridProps> = (props) => {
horizontal,
noFaces,
inLibraryIndicator,
isSelectModeActive,
selectedMangaIds,
handleSelection,
} = props;
const [dimensions, setDimensions] = useState(document.documentElement.offsetWidth);
@@ -221,6 +274,9 @@ export const MangaGrid: React.FC<IMangaGridProps> = (props) => {
inLibraryIndicator={inLibraryIndicator}
GridItemContainer={GridItemContainer}
gridLayout={gridLayout}
isSelectModeActive={isSelectModeActive}
selectedMangaIds={selectedMangaIds}
handleSelection={handleSelection}
/>
) : (
<VerticalGrid
@@ -231,6 +287,9 @@ export const MangaGrid: React.FC<IMangaGridProps> = (props) => {
hasNextPage={hasNextPage}
loadMore={loadMore}
gridLayout={gridLayout}
isSelectModeActive={isSelectModeActive}
selectedMangaIds={selectedMangaIds}
handleSelection={handleSelection}
/>
)}
</div>