mirror of
https://github.com/Radarr/Radarr
synced 2025-01-03 05:44:50 +00:00
Virtualize movie select for manual import with react-window
This commit is contained in:
parent
eeb997430c
commit
41fb0eb7c6
1 changed files with 112 additions and 13 deletions
|
@ -1,5 +1,13 @@
|
|||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { throttle } from 'lodash';
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { FixedSizeList as List, ListChildComponentProps } from 'react-window';
|
||||
import TextInput from 'Components/Form/TextInput';
|
||||
import Button from 'Components/Link/Button';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
|
@ -10,20 +18,100 @@ import Scroller from 'Components/Scroller/Scroller';
|
|||
import { scrollDirections } from 'Helpers/Props';
|
||||
import Movie from 'Movie/Movie';
|
||||
import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector';
|
||||
import dimensions from 'Styles/Variables/dimensions';
|
||||
import SelectMovieRow from './SelectMovieRow';
|
||||
import styles from './SelectMovieModalContent.css';
|
||||
|
||||
const bodyPadding = parseInt(dimensions.pageContentBodyPadding);
|
||||
|
||||
interface SelectMovieModalContentProps {
|
||||
modalTitle: string;
|
||||
onMovieSelect(movie: Movie): void;
|
||||
onModalClose(): void;
|
||||
}
|
||||
|
||||
interface RowItemData {
|
||||
items: Movie[];
|
||||
onMovieSelect(movieId: number): void;
|
||||
}
|
||||
|
||||
const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
|
||||
index,
|
||||
style,
|
||||
data,
|
||||
}) => {
|
||||
const { items, onMovieSelect } = data;
|
||||
|
||||
if (index >= items.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const movie = items[index];
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
<SelectMovieRow
|
||||
id={movie.id}
|
||||
title={movie.title}
|
||||
year={movie.year}
|
||||
onMovieSelect={onMovieSelect}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function SelectMovieModalContent(props: SelectMovieModalContentProps) {
|
||||
const { modalTitle, onMovieSelect, onModalClose } = props;
|
||||
|
||||
const listRef = useRef<List<RowItemData>>(null);
|
||||
const scrollerRef = useRef<HTMLDivElement>(null);
|
||||
const allMovies: Movie[] = useSelector(createAllMoviesSelector());
|
||||
const [filter, setFilter] = useState('');
|
||||
const [size, setSize] = useState({ width: 0, height: 0 });
|
||||
const windowHeight = window.innerHeight;
|
||||
|
||||
useEffect(() => {
|
||||
const current = scrollerRef?.current as HTMLElement;
|
||||
|
||||
if (current) {
|
||||
const width = current.clientWidth;
|
||||
const height = current.clientHeight;
|
||||
const padding = bodyPadding - 5;
|
||||
|
||||
setSize({
|
||||
width: width - padding * 2,
|
||||
height: height + padding,
|
||||
});
|
||||
}
|
||||
}, [windowHeight, scrollerRef]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentScrollerRef = scrollerRef.current as HTMLElement;
|
||||
const currentScrollListener = currentScrollerRef;
|
||||
|
||||
const handleScroll = throttle(() => {
|
||||
const { offsetTop = 0 } = currentScrollerRef;
|
||||
const scrollTop = currentScrollerRef.scrollTop - offsetTop;
|
||||
|
||||
listRef.current?.scrollTo(scrollTop);
|
||||
}, 10);
|
||||
|
||||
currentScrollListener.addEventListener('scroll', handleScroll);
|
||||
|
||||
return () => {
|
||||
handleScroll.cancel();
|
||||
|
||||
if (currentScrollListener) {
|
||||
currentScrollListener.removeEventListener('scroll', handleScroll);
|
||||
}
|
||||
};
|
||||
}, [listRef, scrollerRef]);
|
||||
|
||||
const onFilterChange = useCallback(
|
||||
({ value }: { value: string }) => {
|
||||
|
@ -68,18 +156,29 @@ function SelectMovieModalContent(props: SelectMovieModalContentProps) {
|
|||
onChange={onFilterChange}
|
||||
/>
|
||||
|
||||
<Scroller className={styles.scroller} autoFocus={false}>
|
||||
{items.map((item) => {
|
||||
return (
|
||||
<SelectMovieRow
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
title={item.title}
|
||||
year={item.year}
|
||||
onMovieSelect={onMovieSelectWrapper}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<Scroller
|
||||
className={styles.scroller}
|
||||
autoFocus={false}
|
||||
ref={scrollerRef}
|
||||
>
|
||||
<List<RowItemData>
|
||||
ref={listRef}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
overflow: 'none',
|
||||
}}
|
||||
width={size.width}
|
||||
height={size.height}
|
||||
itemCount={items.length}
|
||||
itemSize={38}
|
||||
itemData={{
|
||||
items,
|
||||
onMovieSelect: onMovieSelectWrapper,
|
||||
}}
|
||||
>
|
||||
{Row}
|
||||
</List>
|
||||
</Scroller>
|
||||
</ModalBody>
|
||||
|
||||
|
|
Loading…
Reference in a new issue