index.js 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. import React from 'react';
  2. import { connect } from 'react-redux';
  3. import PropTypes from 'prop-types';
  4. import ImmutablePropTypes from 'react-immutable-proptypes';
  5. import { expandPublicTimeline, expandCommunityTimeline } from 'mastodon/actions/timelines';
  6. import Masonry from 'react-masonry-infinite';
  7. import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
  8. import DetailedStatusContainer from 'mastodon/features/status/containers/detailed_status_container';
  9. import { debounce } from 'lodash';
  10. import LoadingIndicator from 'mastodon/components/loading_indicator';
  11. const mapStateToProps = (state, { local }) => {
  12. const timeline = state.getIn(['timelines', local ? 'community' : 'public'], ImmutableMap());
  13. return {
  14. statusIds: timeline.get('items', ImmutableList()),
  15. isLoading: timeline.get('isLoading', false),
  16. hasMore: timeline.get('hasMore', false),
  17. };
  18. };
  19. export default @connect(mapStateToProps)
  20. class PublicTimeline extends React.PureComponent {
  21. static propTypes = {
  22. dispatch: PropTypes.func.isRequired,
  23. statusIds: ImmutablePropTypes.list.isRequired,
  24. isLoading: PropTypes.bool.isRequired,
  25. hasMore: PropTypes.bool.isRequired,
  26. local: PropTypes.bool,
  27. };
  28. componentDidMount () {
  29. this._connect();
  30. }
  31. componentDidUpdate (prevProps) {
  32. if (prevProps.local !== this.props.local) {
  33. this._connect();
  34. }
  35. }
  36. _connect () {
  37. const { dispatch, local } = this.props;
  38. dispatch(local ? expandCommunityTimeline() : expandPublicTimeline());
  39. }
  40. handleLoadMore = () => {
  41. const { dispatch, statusIds, local } = this.props;
  42. const maxId = statusIds.last();
  43. if (maxId) {
  44. dispatch(local ? expandCommunityTimeline({ maxId }) : expandPublicTimeline({ maxId }));
  45. }
  46. }
  47. setRef = c => {
  48. this.masonry = c;
  49. }
  50. handleHeightChange = debounce(() => {
  51. if (!this.masonry) {
  52. return;
  53. }
  54. this.masonry.forcePack();
  55. }, 50)
  56. render () {
  57. const { statusIds, hasMore, isLoading } = this.props;
  58. const sizes = [
  59. { columns: 1, gutter: 0 },
  60. { mq: '415px', columns: 1, gutter: 10 },
  61. { mq: '640px', columns: 2, gutter: 10 },
  62. { mq: '960px', columns: 3, gutter: 10 },
  63. { mq: '1255px', columns: 3, gutter: 10 },
  64. ];
  65. const loader = (isLoading && statusIds.isEmpty()) ? <LoadingIndicator key={0} /> : undefined;
  66. return (
  67. <Masonry ref={this.setRef} className='statuses-grid' hasMore={hasMore} loadMore={this.handleLoadMore} sizes={sizes} loader={loader}>
  68. {statusIds.map(statusId => (
  69. <div className='statuses-grid__item' key={statusId}>
  70. <DetailedStatusContainer
  71. id={statusId}
  72. compact
  73. measureHeight
  74. onHeightChange={this.handleHeightChange}
  75. />
  76. </div>
  77. )).toArray()}
  78. </Masonry>
  79. );
  80. }
  81. }