songs.tsx 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import React, { CSSProperties, useContext, useMemo, useRef } from 'react';
  2. import AutoSizer from 'react-virtualized-auto-sizer';
  3. import { FixedSizeList as List } from 'react-window';
  4. import { StateContext } from '../../../../context/state';
  5. import { Song } from '../../../../types';
  6. import { namedMemo } from '../../../../utils/component';
  7. import { CmusUIStateContext } from '../reducer';
  8. import { getFilteredSongs } from '../selectors';
  9. import { NoWrapFill } from '../styled/layout';
  10. import { AsciiSpinner } from '../styled/spinner';
  11. import { getSongScrollIndex, lineHeight, useAutoJumpyScroll } from '../utils/scroll';
  12. import * as Styled from './songs.styles';
  13. type Props = {
  14. active: boolean;
  15. };
  16. type SongData = {
  17. song: Song;
  18. active: boolean;
  19. parentActive: boolean;
  20. highlight: boolean;
  21. };
  22. const itemKey = (index: number, data: SongData[]): number => data[index].song.id;
  23. const Row = namedMemo<{ index: number; data: SongData[]; style: CSSProperties }>(
  24. 'Song',
  25. ({ index, data, style }) => {
  26. const { song, active, parentActive, highlight } = data[index];
  27. return (
  28. <Styled.Song active={active} parentActive={parentActive} style={style} highlight={highlight}>
  29. <NoWrapFill>
  30. {song.track ? `${song.track} - ` : ''}
  31. {song.title || 'Untitled Track'}
  32. </NoWrapFill>
  33. </Styled.Song>
  34. );
  35. },
  36. );
  37. export const Songs: React.FC<Props> = ({ active: parentActive }) => {
  38. const globalState = useContext(StateContext);
  39. const { songId: playingSongId } = globalState.player;
  40. const state = useContext(CmusUIStateContext);
  41. const { activeArtist, activeSongId } = state.library;
  42. const filteredSongs = getFilteredSongs(state);
  43. const itemData = useMemo<SongData[]>(
  44. () =>
  45. filteredSongs.map<SongData>((song) => ({
  46. song,
  47. active: song.id === activeSongId,
  48. parentActive,
  49. highlight: song.id === playingSongId,
  50. })),
  51. [parentActive, activeSongId, playingSongId, filteredSongs],
  52. );
  53. const windowRef = useRef<HTMLDivElement>(null);
  54. const scrollIndex = getSongScrollIndex(filteredSongs, activeSongId);
  55. useAutoJumpyScroll(windowRef, scrollIndex);
  56. if (activeArtist !== null && !(activeArtist in state.artistSongs)) {
  57. return (
  58. <Styled.Container>
  59. <AsciiSpinner />
  60. </Styled.Container>
  61. );
  62. }
  63. return (
  64. <Styled.Container>
  65. <AutoSizer>
  66. {({ height, width }): React.ReactElement => (
  67. <List
  68. outerRef={windowRef}
  69. height={height}
  70. width={width}
  71. itemCount={itemData.length}
  72. itemSize={lineHeight}
  73. itemKey={itemKey}
  74. itemData={itemData}
  75. >
  76. {Row}
  77. </List>
  78. )}
  79. </AutoSizer>
  80. </Styled.Container>
  81. );
  82. };