Improve manga grid/list rendering

This commit is contained in:
schroda
2025-01-13 01:29:40 +01:00
parent 63a6cfcfb3
commit 74c61a5a29
6 changed files with 372 additions and 363 deletions

View File

@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { useRef, useState } from 'react';
import { useCallback, useRef, useState } from 'react';
export type SelectableCollectionReturnType<Id extends number | string, Key extends string = string> = {
selectedItemIds: Id[];
@@ -52,11 +52,8 @@ export const useSelectableCollection = <Id extends number | string, Key extends
lastSelectedItemInfoRef.current = undefined;
}
const handleSelection: SelectableCollectionReturnType<Id, Key>['handleSelection'] = (
id,
selected,
{ selectRange = false, key = currentKey } = {},
) => {
const handleSelection: SelectableCollectionReturnType<Id, Key>['handleSelection'] = useCallback(
(id, selected, { selectRange = false, key = currentKey } = {}) => {
const deselect = !selected;
const { id: lastSelectedItemId, key: lastSelectedItemIdKey } = lastSelectedItemInfoRef.current ?? {};
@@ -86,9 +83,12 @@ export const useSelectableCollection = <Id extends number | string, Key extends
...prevState,
[key]: [...new Set([...(prevState[key] ?? []), ...selectedIds])],
}));
};
},
[currentKey, itemIds],
);
const handleSelectAll = (selectAll: boolean, ids: Id[], key: Key = currentKey) => {
const handleSelectAll = useCallback(
(selectAll: boolean, ids: Id[], key: Key = currentKey) => {
switch (selectAll) {
case true:
setKeyToSelectedItemIds((prevState) => ({
@@ -105,20 +105,22 @@ export const useSelectableCollection = <Id extends number | string, Key extends
default:
break;
}
};
},
[currentKey],
);
const setSelectionForKey = (key: Key, ids: Id[]) => {
const setSelectionForKey = useCallback((key: Key, ids: Id[]) => {
setKeyToSelectedItemIds((prevState) => ({
...prevState,
[key]: [...ids],
}));
};
}, []);
const getSelectionForKey = (key: Key) => keyToSelectedItemIds[key];
const getSelectionForKey = useCallback((key: Key) => keyToSelectedItemIds[key], [keyToSelectedItemIds]);
const clearSelection = () => {
const clearSelection = useCallback(() => {
setKeyToSelectedItemIds({});
};
}, []);
return {
selectedItemIds,

View File

@@ -19,6 +19,8 @@ interface LibraryMangaGridProps
isLoading: boolean;
}
const loadMoreNoop = () => undefined;
export const LibraryMangaGrid: React.FC<LibraryMangaGridProps> = ({
showFilteredOutMessage,
message,
@@ -41,7 +43,7 @@ export const LibraryMangaGrid: React.FC<LibraryMangaGridProps> = ({
gridWrapperProps={{ sx: { p: 1 } }}
{...gridProps}
hasNextPage={false}
loadMore={() => undefined}
loadMore={loadMoreNoop}
message={showFilteredOutMessage ? t('library.error.label.no_matches') : message}
messageExtra={showFilteredOutMessage ? undefined : messageExtra}
gridLayout={options.gridLayout}

View File

@@ -122,10 +122,13 @@ export function Library() {
currentKey: activeTab?.id.toString(),
});
const handleSelect: typeof handleSelection = (id, selected, selectOptions) => {
const handleSelect: typeof handleSelection = useCallback(
(id, selected, selectOptions) => {
setIsSelectModeActive(!!(selectedItemIds.length + (selected ? 1 : -1)));
handleSelection(id, selected, selectOptions);
};
},
[setIsSelectModeActive, handleSelection],
);
const selectedMangas = useMemo(
() =>
@@ -212,7 +215,6 @@ export function Library() {
isSelectModeActive,
areNoItemsSelected,
areAllItemsSelected,
selectedItemIds.length,
mangas.length,
activeTab,
showTabSize,

View File

@@ -7,7 +7,7 @@
*/
import PopupState, { bindMenu } from 'material-ui-popup-state';
import { useMemo, useState } from 'react';
import { memo, useCallback, useMemo, useState } from 'react';
import { useLongPress } from 'use-long-press';
import { MangaActionMenuItems, SingleModeProps } from '@/modules/manga/components/MangaActionMenuItems.tsx';
import { Menu } from '@/modules/core/components/menu/Menu.tsx';
@@ -42,7 +42,7 @@ const getMangaLinkTo = (
}
};
export const MangaCard = (props: MangaCardProps) => {
export const MangaCard = memo((props: MangaCardProps) => {
const { manga, gridLayout, inLibraryIndicator, selected, handleSelection, mode = 'default' } = props;
const { id, firstUnreadChapter, downloadCount, unreadCount } = manga;
const {
@@ -58,7 +58,8 @@ export const MangaCard = (props: MangaCardProps) => {
const [isMigrateDialogOpen, setIsMigrateDialogOpen] = useState(false);
const handleClick = (event: React.MouseEvent | React.TouchEvent, openMenu?: () => void) => {
const handleClick = useCallback(
(event: React.MouseEvent | React.TouchEvent, openMenu?: () => void) => {
const isDefaultMode = mode === 'default';
const isSourceMode = mode === 'source';
const isMigrateSelectMode = mode === 'migrate.select';
@@ -91,43 +92,25 @@ export const MangaCard = (props: MangaCardProps) => {
if (isMigrateSelectMode) {
setIsMigrateDialogOpen(true);
}
};
},
[mode, selected, updateLibraryState, handleSelection],
);
const longPressBind = useLongPress((e, { context }) => {
const longPressBind = useLongPress(
useCallback(
(e: any, { context }: any) => {
e.shiftKey = true;
handleClick(e, context as () => {});
});
},
[handleClick],
),
);
const MangaCardComponent = useMemo(
() => (gridLayout === GridLayout.List ? MangaListCard : MangaGridCard),
[gridLayout],
);
const continueReadingButton = useMemo(
() => (
<ContinueReadingButton
showContinueReadingButton={showContinueReadingButton && mode === 'default'}
chapter={firstUnreadChapter}
mangaLinkTo={mangaLinkTo}
/>
),
[showContinueReadingButton, firstUnreadChapter, mangaLinkTo],
);
const mangaBadges = useMemo(
() => (
<MangaBadges
inLibraryIndicator={inLibraryIndicator}
isInLibrary={isInLibrary}
unread={unreadCount}
downloadCount={downloadCount}
updateLibraryState={updateLibraryState}
mode={mode}
/>
),
[inLibraryIndicator, isInLibrary, unreadCount, downloadCount, updateLibraryState],
);
return (
<>
{isMigrateDialogOpen && (
@@ -144,8 +127,23 @@ export const MangaCard = (props: MangaCardProps) => {
mangaLinkTo={mangaLinkTo}
isInLibrary={isInLibrary}
inLibraryIndicator={inLibraryIndicator}
continueReadingButton={continueReadingButton}
mangaBadges={mangaBadges}
continueReadingButton={
<ContinueReadingButton
showContinueReadingButton={showContinueReadingButton && mode === 'default'}
chapter={firstUnreadChapter}
mangaLinkTo={mangaLinkTo}
/>
}
mangaBadges={
<MangaBadges
inLibraryIndicator={inLibraryIndicator}
isInLibrary={isInLibrary}
unread={unreadCount}
downloadCount={downloadCount}
updateLibraryState={updateLibraryState}
mode={mode}
/>
}
/>
{!!handleSelection && popupState.isOpen && (
<Menu {...bindMenu(popupState)}>
@@ -165,4 +163,4 @@ export const MangaCard = (props: MangaCardProps) => {
</PopupState>
</>
);
};
});

View File

@@ -14,7 +14,7 @@ import CardActionArea from '@mui/material/CardActionArea';
import Stack from '@mui/material/Stack';
import Tooltip from '@mui/material/Tooltip';
import { styled } from '@mui/material/styles';
import { useRef } from 'react';
import { memo, useRef } from 'react';
import { SpinnerImage } from '@/modules/core/components/SpinnerImage.tsx';
import { MangaOptionButton } from '@/modules/manga/components/MangaOptionButton.tsx';
import { Mangas } from '@/modules/manga/services/Mangas.ts';
@@ -39,7 +39,8 @@ const BottomGradientDoubledDown = styled('div')({
background: 'linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 100%)',
});
export const MangaGridCard = ({
export const MangaGridCard = memo(
({
manga,
longPressBind,
popupState,
@@ -188,7 +189,8 @@ export const MangaGridCard = ({
<TypographyMaxLines
component="h3"
sx={{
color: (theme) => (selected ? theme.palette.primary.contrastText : 'text.primary'),
color: (theme) =>
selected ? theme.palette.primary.contrastText : 'text.primary',
height: '3rem',
pt: 0.5,
}}
@@ -201,4 +203,5 @@ export const MangaGridCard = ({
</Box>
</Link>
);
};
},
);

View File

@@ -14,14 +14,15 @@ import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import Tooltip from '@mui/material/Tooltip';
import { Link as RouterLink } from 'react-router-dom';
import { useRef } from 'react';
import { memo, useRef } from 'react';
import { SpinnerImage } from '@/modules/core/components/SpinnerImage.tsx';
import { TypographyMaxLines } from '@/modules/core/components/TypographyMaxLines.tsx';
import { SpecificMangaCardProps } from '@/modules/manga/Manga.types.ts';
import { Mangas } from '@/modules/manga/services/Mangas.ts';
import { MangaOptionButton } from '@/modules/manga/components/MangaOptionButton.tsx';
export const MangaListCard = ({
export const MangaListCard = memo(
({
manga,
longPressBind,
popupState,
@@ -130,4 +131,5 @@ export const MangaListCard = ({
</CardActionArea>
</Card>
);
};
},
);