|
|
@@ -1,13 +1,5 @@
|
|
|
-import { useDebounce, useDebounceCallback } from '@react-hook/debounce';
|
|
|
-import React, {
|
|
|
- CSSProperties,
|
|
|
- useCallback,
|
|
|
- useContext,
|
|
|
- useEffect,
|
|
|
- useMemo,
|
|
|
- useRef,
|
|
|
- useState,
|
|
|
-} from 'react';
|
|
|
+import { useDebounce } from '@react-hook/debounce';
|
|
|
+import React, { CSSProperties, useContext, useEffect, useMemo, useRef } from 'react';
|
|
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
|
|
import { FixedSizeList as List } from 'react-window';
|
|
|
|
|
|
@@ -17,12 +9,13 @@ import { artistAlbumsLoaded, artistSongsLoaded } from '../actions';
|
|
|
import { CmusUIDispatchContext, CmusUIStateContext } from '../reducer';
|
|
|
import { NoWrapFill } from '../styled/layout';
|
|
|
import { AsciiSpinner } from '../styled/spinner';
|
|
|
-import { getScrollIndex } from '../utils/scroll';
|
|
|
+import { getArtistAlbumScrollIndex, lineHeight, useAutoJumpyScroll } from '../utils/scroll';
|
|
|
|
|
|
import * as Styled from './artists.styles';
|
|
|
|
|
|
-type Props = {
|
|
|
+export type Props = {
|
|
|
active: boolean;
|
|
|
+ currentArtist: string | null;
|
|
|
};
|
|
|
|
|
|
type ArtistData = {
|
|
|
@@ -31,6 +24,7 @@ type ArtistData = {
|
|
|
loading: boolean;
|
|
|
active: boolean;
|
|
|
parentActive: boolean;
|
|
|
+ highlight: boolean;
|
|
|
};
|
|
|
|
|
|
type AlbumData = {
|
|
|
@@ -48,8 +42,13 @@ const itemKey = (index: number, data: RowData[]): string => data[index].id;
|
|
|
|
|
|
const Artist = namedMemo<{ row: ArtistData; style: CSSProperties }>(
|
|
|
'Artist',
|
|
|
- ({ row: { artist, loading, active, parentActive }, style }) => (
|
|
|
- <Styled.ArtistTitle active={active} parentActive={parentActive} style={style}>
|
|
|
+ ({ row: { artist, loading, active, parentActive, highlight }, style }) => (
|
|
|
+ <Styled.ArtistTitle
|
|
|
+ active={active}
|
|
|
+ parentActive={parentActive}
|
|
|
+ style={style}
|
|
|
+ highlight={highlight}
|
|
|
+ >
|
|
|
{loading ? <AsciiSpinner /> : <> </>}
|
|
|
<NoWrapFill>{artist || 'Unknown Artist'}</NoWrapFill>
|
|
|
</Styled.ArtistTitle>
|
|
|
@@ -76,10 +75,7 @@ const Row = namedMemo<{ index: number; data: RowData[]; style: CSSProperties }>(
|
|
|
},
|
|
|
);
|
|
|
|
|
|
-const lineHeight = 16;
|
|
|
-const scrollThresholdLines = 4;
|
|
|
-
|
|
|
-export const Artists: React.FC<Props> = ({ active: parentActive }) => {
|
|
|
+export const Artists: React.FC<Props> = ({ active: parentActive, currentArtist }) => {
|
|
|
const dispatchUI = useContext(CmusUIDispatchContext);
|
|
|
const state = useContext(CmusUIStateContext);
|
|
|
const {
|
|
|
@@ -88,7 +84,7 @@ export const Artists: React.FC<Props> = ({ active: parentActive }) => {
|
|
|
library: { activeArtist, activeAlbum, expandedArtists },
|
|
|
} = state;
|
|
|
|
|
|
- const [debouncedActiveArtist, setDebouncedActiveArtist] = useDebounce(activeArtist, 100);
|
|
|
+ const [debouncedActiveArtist, setDebouncedActiveArtist] = useDebounce(activeArtist, 200);
|
|
|
useEffect(() => {
|
|
|
setDebouncedActiveArtist(activeArtist);
|
|
|
}, [activeArtist, setDebouncedActiveArtist]);
|
|
|
@@ -119,6 +115,7 @@ export const Artists: React.FC<Props> = ({ active: parentActive }) => {
|
|
|
loading: !(artist in artistAlbums) && expandedArtists.includes(artist),
|
|
|
active: activeArtist === artist && activeAlbum === null,
|
|
|
parentActive,
|
|
|
+ highlight: currentArtist === artist,
|
|
|
};
|
|
|
|
|
|
if (!expanded) {
|
|
|
@@ -136,32 +133,19 @@ export const Artists: React.FC<Props> = ({ active: parentActive }) => {
|
|
|
})),
|
|
|
];
|
|
|
}, []),
|
|
|
- [parentActive, artists, artistAlbums, activeArtist, activeAlbum, expandedArtists],
|
|
|
+ [
|
|
|
+ parentActive,
|
|
|
+ artists,
|
|
|
+ artistAlbums,
|
|
|
+ activeArtist,
|
|
|
+ activeAlbum,
|
|
|
+ expandedArtists,
|
|
|
+ currentArtist,
|
|
|
+ ],
|
|
|
);
|
|
|
|
|
|
- const ref = useRef<HTMLDivElement>(null);
|
|
|
- const [windowDimensions, setWindowDimensions] = useState<{ width: number; height: number }>({
|
|
|
- width: 0,
|
|
|
- height: 0,
|
|
|
- });
|
|
|
- const onResize = useCallback(() => {
|
|
|
- setWindowDimensions({
|
|
|
- width: ref.current?.offsetWidth ?? 0,
|
|
|
- height: ref.current?.offsetHeight ?? 0,
|
|
|
- });
|
|
|
- }, []);
|
|
|
- const resizeHandler = useDebounceCallback(onResize, 100);
|
|
|
-
|
|
|
- useEffect(() => {
|
|
|
- onResize();
|
|
|
- window.addEventListener('resize', resizeHandler);
|
|
|
- return (): void => {
|
|
|
- window.removeEventListener('resize', resizeHandler);
|
|
|
- };
|
|
|
- }, [onResize, resizeHandler]);
|
|
|
-
|
|
|
const windowRef = useRef<HTMLDivElement>(null);
|
|
|
- const scrollIndex = getScrollIndex(
|
|
|
+ const scrollIndex = getArtistAlbumScrollIndex(
|
|
|
state.artists,
|
|
|
state.artistAlbums,
|
|
|
state.library.activeArtist,
|
|
|
@@ -169,29 +153,10 @@ export const Artists: React.FC<Props> = ({ active: parentActive }) => {
|
|
|
state.library.expandedArtists,
|
|
|
);
|
|
|
|
|
|
- useEffect(() => {
|
|
|
- if (!windowRef.current) {
|
|
|
- return;
|
|
|
- }
|
|
|
- const heightInLines = Math.floor(windowDimensions.height / lineHeight);
|
|
|
- if (heightInLines < scrollThresholdLines + 1) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- const scrollPosLines = Math.floor(windowRef.current.scrollTop / lineHeight);
|
|
|
-
|
|
|
- const linesBefore = scrollIndex - scrollPosLines;
|
|
|
- const linesAfter = scrollPosLines + heightInLines - scrollIndex;
|
|
|
-
|
|
|
- if (linesAfter < scrollThresholdLines) {
|
|
|
- windowRef.current.scrollTop += lineHeight;
|
|
|
- } else if (linesBefore < scrollThresholdLines) {
|
|
|
- windowRef.current.scrollTop -= lineHeight;
|
|
|
- }
|
|
|
- }, [windowDimensions.height, scrollIndex]);
|
|
|
+ useAutoJumpyScroll(windowRef, scrollIndex);
|
|
|
|
|
|
return (
|
|
|
- <Styled.Container ref={ref}>
|
|
|
+ <Styled.Container>
|
|
|
<AutoSizer>
|
|
|
{({ height, width }): React.ReactElement => (
|
|
|
<List
|