Refactor Artist index to use react-window

(cherry picked from commit d022679b7dcbce3cec98e6a1fd0879e3c0d92523)

Fixed: Restoring scroll position when going back/forward to artist list

(cherry picked from commit 5aad84dba453c42b4b5a9eac43deecf91a98f4f6)
This commit is contained in:
Mark McDowall 2023-01-05 18:20:49 -08:00 committed by Bogdan
parent 01e21c09db
commit f509ca0f72
117 changed files with 4704 additions and 5511 deletions

View file

@ -23,6 +23,8 @@ function SpinnerIconButton(props) {
}
SpinnerIconButton.propTypes = {
...IconButton.propTypes,
className: PropTypes.string,
name: PropTypes.object.isRequired,
spinningName: PropTypes.object.isRequired,
isDisabled: PropTypes.bool.isRequired,

View file

@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import Menu from 'Components/Menu/Menu';
import ToolbarMenuButton from 'Components/Menu/ToolbarMenuButton';
import { icons } from 'Helpers/Props';
import { align, icons } from 'Helpers/Props';
function SortMenu(props) {
const {
@ -30,7 +30,8 @@ function SortMenu(props) {
SortMenu.propTypes = {
className: PropTypes.string,
children: PropTypes.node.isRequired,
isDisabled: PropTypes.bool.isRequired
isDisabled: PropTypes.bool.isRequired,
alignMenu: PropTypes.oneOf([align.LEFT, align.RIGHT])
};
SortMenu.defaultProps = {

View file

@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import Menu from 'Components/Menu/Menu';
import ToolbarMenuButton from 'Components/Menu/ToolbarMenuButton';
import { icons } from 'Helpers/Props';
import { align, icons } from 'Helpers/Props';
function ViewMenu(props) {
const {
@ -27,7 +27,8 @@ function ViewMenu(props) {
ViewMenu.propTypes = {
children: PropTypes.node.isRequired,
isDisabled: PropTypes.bool.isRequired
isDisabled: PropTypes.bool.isRequired,
alignMenu: PropTypes.oneOf([align.LEFT, align.RIGHT])
};
ViewMenu.defaultProps = {

View file

@ -1,61 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Scroller from 'Components/Scroller/Scroller';
import { scrollDirections } from 'Helpers/Props';
import { isLocked } from 'Utilities/scrollLock';
import styles from './PageContentBody.css';
class PageContentBody extends Component {
//
// Listeners
onScroll = (props) => {
const { onScroll } = this.props;
if (this.props.onScroll && !isLocked()) {
onScroll(props);
}
};
//
// Render
render() {
const {
className,
innerClassName,
children,
dispatch,
...otherProps
} = this.props;
return (
<Scroller
className={className}
scrollDirection={scrollDirections.VERTICAL}
{...otherProps}
onScroll={this.onScroll}
>
<div className={innerClassName}>
{children}
</div>
</Scroller>
);
}
}
PageContentBody.propTypes = {
className: PropTypes.string,
innerClassName: PropTypes.string,
children: PropTypes.node.isRequired,
onScroll: PropTypes.func,
dispatch: PropTypes.func
};
PageContentBody.defaultProps = {
className: styles.contentBody,
innerClassName: styles.innerContentBody
};
export default PageContentBody;

View file

@ -0,0 +1,51 @@
import React, { forwardRef, ReactNode, useCallback } from 'react';
import Scroller from 'Components/Scroller/Scroller';
import ScrollDirection from 'Helpers/Props/ScrollDirection';
import { isLocked } from 'Utilities/scrollLock';
import styles from './PageContentBody.css';
interface PageContentBodyProps {
className?: string;
innerClassName?: string;
children: ReactNode;
initialScrollTop?: number;
onScroll?: (payload) => void;
}
const PageContentBody = forwardRef(
(
props: PageContentBodyProps,
ref: React.MutableRefObject<HTMLDivElement>
) => {
const {
className = styles.contentBody,
innerClassName = styles.innerContentBody,
children,
onScroll,
...otherProps
} = props;
const onScrollWrapper = useCallback(
(payload) => {
if (onScroll && !isLocked()) {
onScroll(payload);
}
},
[onScroll]
);
return (
<Scroller
ref={ref}
{...otherProps}
className={className}
scrollDirection={ScrollDirection.Vertical}
onScroll={onScrollWrapper}
>
<div className={innerClassName}>{children}</div>
</Scroller>
);
}
);
export default PageContentBody;

View file

@ -45,7 +45,8 @@ PageToolbarButton.propTypes = {
iconName: PropTypes.object.isRequired,
spinningName: PropTypes.object,
isSpinning: PropTypes.bool,
isDisabled: PropTypes.bool
isDisabled: PropTypes.bool,
onPress: PropTypes.func
};
PageToolbarButton.defaultProps = {

View file

@ -1,95 +0,0 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { scrollDirections } from 'Helpers/Props';
import styles from './Scroller.css';
class Scroller extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this._scroller = null;
}
componentDidMount() {
const {
scrollDirection,
autoFocus,
scrollTop
} = this.props;
if (this.props.scrollTop != null) {
this._scroller.scrollTop = scrollTop;
}
if (autoFocus && scrollDirection !== scrollDirections.NONE) {
this._scroller.focus({ preventScroll: true });
}
}
//
// Control
_setScrollerRef = (ref) => {
this._scroller = ref;
this.props.registerScroller(ref);
};
//
// Render
render() {
const {
className,
scrollDirection,
autoScroll,
children,
scrollTop,
onScroll,
registerScroller,
...otherProps
} = this.props;
return (
<div
ref={this._setScrollerRef}
className={classNames(
className,
styles.scroller,
styles[scrollDirection],
autoScroll && styles.autoScroll
)}
tabIndex={-1}
{...otherProps}
>
{children}
</div>
);
}
}
Scroller.propTypes = {
className: PropTypes.string,
scrollDirection: PropTypes.oneOf(scrollDirections.all).isRequired,
autoFocus: PropTypes.bool.isRequired,
autoScroll: PropTypes.bool.isRequired,
scrollTop: PropTypes.number,
children: PropTypes.node,
onScroll: PropTypes.func,
registerScroller: PropTypes.func
};
Scroller.defaultProps = {
scrollDirection: scrollDirections.VERTICAL,
autoFocus: true,
autoScroll: true,
registerScroller: () => { /* no-op */ }
};
export default Scroller;

View file

@ -0,0 +1,90 @@
import classNames from 'classnames';
import { throttle } from 'lodash';
import React, { forwardRef, ReactNode, useEffect, useRef } from 'react';
import ScrollDirection from 'Helpers/Props/ScrollDirection';
import styles from './Scroller.css';
interface ScrollerProps {
className?: string;
scrollDirection?: ScrollDirection;
autoFocus?: boolean;
autoScroll?: boolean;
scrollTop?: number;
initialScrollTop?: number;
children?: ReactNode;
onScroll?: (payload) => void;
}
const Scroller = forwardRef(
(props: ScrollerProps, ref: React.MutableRefObject<HTMLDivElement>) => {
const {
className,
autoFocus = false,
autoScroll = true,
scrollDirection = ScrollDirection.Vertical,
children,
scrollTop,
initialScrollTop,
onScroll,
...otherProps
} = props;
const internalRef = useRef();
const currentRef = ref ?? internalRef;
useEffect(
() => {
if (initialScrollTop != null) {
currentRef.current.scrollTop = initialScrollTop;
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
useEffect(() => {
if (scrollTop != null) {
currentRef.current.scrollTop = scrollTop;
}
if (autoFocus && scrollDirection !== ScrollDirection.None) {
currentRef.current.focus({ preventScroll: true });
}
}, [autoFocus, currentRef, scrollDirection, scrollTop]);
useEffect(() => {
const div = currentRef.current;
const handleScroll = throttle(() => {
const scrollLeft = div.scrollLeft;
const scrollTop = div.scrollTop;
onScroll?.({ scrollLeft, scrollTop });
}, 10);
div.addEventListener('scroll', handleScroll);
return () => {
div.removeEventListener('scroll', handleScroll);
};
}, [currentRef, onScroll]);
return (
<div
{...otherProps}
ref={currentRef}
className={classNames(
className,
styles.scroller,
styles[scrollDirection],
autoScroll && styles.autoScroll
)}
tabIndex={-1}
>
{children}
</div>
);
}
);
export default Scroller;

View file

@ -1,30 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import scrollPositions from 'Store/scrollPositions';
function withScrollPosition(WrappedComponent, scrollPositionKey) {
function ScrollPosition(props) {
const {
history
} = props;
const scrollTop = history.action === 'POP' || (history.location.state && history.location.state.restoreScrollPosition) ?
scrollPositions[scrollPositionKey] :
0;
return (
<WrappedComponent
{...props}
scrollTop={scrollTop}
/>
);
}
ScrollPosition.propTypes = {
history: PropTypes.object.isRequired
};
return ScrollPosition;
}
export default withScrollPosition;

View file

@ -0,0 +1,25 @@
import PropTypes from 'prop-types';
import React from 'react';
import scrollPositions from 'Store/scrollPositions';
function withScrollPosition(WrappedComponent, scrollPositionKey) {
function ScrollPosition(props) {
const { history } = props;
const initialScrollTop =
history.action === 'POP' ||
(history.location.state && history.location.state.restoreScrollPosition)
? scrollPositions[scrollPositionKey]
: 0;
return <WrappedComponent {...props} initialScrollTop={initialScrollTop} />;
}
ScrollPosition.propTypes = {
history: PropTypes.object.isRequired,
};
return ScrollPosition;
}
export default withScrollPosition;