You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Lidarr/frontend/src/Artist/Index/Overview/ArtistIndexOverviews.tsx

204 lines
5.4 KiB

import { throttle } from 'lodash';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { FixedSizeList as List, ListChildComponentProps } from 'react-window';
import Artist from 'Artist/Artist';
import useMeasure from 'Helpers/Hooks/useMeasure';
import dimensions from 'Styles/Variables/dimensions';
import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter';
import ArtistIndexOverview from './ArtistIndexOverview';
import selectOverviewOptions from './selectOverviewOptions';
// Poster container dimensions
const columnPadding = parseInt(dimensions.artistIndexColumnPadding);
const columnPaddingSmallScreen = parseInt(
dimensions.artistIndexColumnPaddingSmallScreen
);
const progressBarHeight = parseInt(dimensions.progressBarSmallHeight);
const detailedProgressBarHeight = parseInt(dimensions.progressBarMediumHeight);
const bodyPadding = parseInt(dimensions.pageContentBodyPadding);
const bodyPaddingSmallScreen = parseInt(
dimensions.pageContentBodyPaddingSmallScreen
);
interface RowItemData {
items: Artist[];
sortKey: string;
posterWidth: number;
posterHeight: number;
rowHeight: number;
isSmallScreen: boolean;
}
interface ArtistIndexOverviewsProps {
items: Artist[];
sortKey?: string;
sortDirection?: string;
jumpToCharacter?: string;
scrollTop?: number;
scrollerRef: React.MutableRefObject<HTMLElement>;
isSmallScreen: boolean;
}
const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
index,
style,
data,
}) => {
const { items, ...otherData } = data;
if (index >= items.length) {
return null;
}
const artist = items[index];
return (
<div style={style}>
<ArtistIndexOverview artistId={artist.id} {...otherData} />
</div>
);
};
function getWindowScrollTopPosition() {
return document.documentElement.scrollTop || document.body.scrollTop || 0;
}
function ArtistIndexOverviews(props: ArtistIndexOverviewsProps) {
const { items, sortKey, jumpToCharacter, isSmallScreen, scrollerRef } = props;
const { size: posterSize, detailedProgressBar } = useSelector(
selectOverviewOptions
);
const listRef: React.MutableRefObject<List> = useRef();
const [measureRef, bounds] = useMeasure();
const [size, setSize] = useState({ width: 0, height: 0 });
const posterWidth = useMemo(() => {
const maxiumPosterWidth = isSmallScreen ? 192 : 202;
if (posterSize === 'large') {
return maxiumPosterWidth;
}
if (posterSize === 'medium') {
return Math.floor(maxiumPosterWidth * 0.75);
}
return Math.floor(maxiumPosterWidth * 0.5);
}, [posterSize, isSmallScreen]);
const posterHeight = useMemo(() => {
return posterWidth;
}, [posterWidth]);
const rowHeight = useMemo(() => {
const heights = [
posterHeight,
detailedProgressBar ? detailedProgressBarHeight : progressBarHeight,
isSmallScreen ? columnPaddingSmallScreen : columnPadding,
];
return heights.reduce((acc, height) => acc + height, 0);
}, [detailedProgressBar, posterHeight, isSmallScreen]);
useEffect(() => {
const current = scrollerRef.current as HTMLElement;
if (isSmallScreen) {
setSize({
width: window.innerWidth,
height: window.innerHeight,
});
return;
}
if (current) {
const width = current.clientWidth;
const padding =
(isSmallScreen ? bodyPaddingSmallScreen : bodyPadding) - 5;
setSize({
width: width - padding * 2,
height: window.innerHeight,
});
}
}, [isSmallScreen, scrollerRef, bounds]);
useEffect(() => {
const currentScrollListener = isSmallScreen ? window : scrollerRef.current;
const currentScrollerRef = scrollerRef.current;
const handleScroll = throttle(() => {
const { offsetTop = 0 } = currentScrollerRef;
const scrollTop =
(isSmallScreen
? getWindowScrollTopPosition()
: currentScrollerRef.scrollTop) - offsetTop;
listRef.current.scrollTo(scrollTop);
}, 10);
currentScrollListener.addEventListener('scroll', handleScroll);
return () => {
handleScroll.cancel();
if (currentScrollListener) {
currentScrollListener.removeEventListener('scroll', handleScroll);
}
};
}, [isSmallScreen, listRef, scrollerRef]);
useEffect(() => {
if (jumpToCharacter) {
const index = getIndexOfFirstCharacter(items, jumpToCharacter);
if (index != null) {
let scrollTop = index * rowHeight;
// If the offset is zero go to the top, otherwise offset
// by the approximate size of the header + padding (37 + 20).
if (scrollTop > 0) {
const offset = 57;
scrollTop += offset;
}
listRef.current.scrollTo(scrollTop);
scrollerRef.current.scrollTo(0, scrollTop);
}
}
}, [jumpToCharacter, rowHeight, items, scrollerRef, listRef]);
return (
<div ref={measureRef}>
<List<RowItemData>
ref={listRef}
style={{
width: '100%',
height: '100%',
overflow: 'none',
}}
width={size.width}
height={size.height}
itemCount={items.length}
itemSize={rowHeight}
itemData={{
items,
sortKey,
posterWidth,
posterHeight,
rowHeight,
isSmallScreen,
}}
>
{Row}
</List>
</div>
);
}
export default ArtistIndexOverviews;