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.

159 lines
5.0 KiB

  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import { injectIntl } from 'react-intl';
  4. import ImmutablePropTypes from 'react-immutable-proptypes';
  5. import ImmutablePureComponent from 'react-immutable-pure-component';
  6. import ReactSwipeableViews from 'react-swipeable-views';
  7. import { links, getIndex, getLink } from './tabs_bar';
  8. import BundleContainer from '../containers/bundle_container';
  9. import ColumnLoading from './column_loading';
  10. import BundleColumnError from './bundle_column_error';
  11. import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, FavouritedStatuses } from '../../ui/util/async-components';
  12. import detectPassiveEvents from 'detect-passive-events';
  13. import { scrollRight } from '../../../scroll';
  14. const componentMap = {
  15. 'COMPOSE': Compose,
  16. 'HOME': HomeTimeline,
  17. 'NOTIFICATIONS': Notifications,
  18. 'PUBLIC': PublicTimeline,
  19. 'COMMUNITY': CommunityTimeline,
  20. 'HASHTAG': HashtagTimeline,
  21. 'FAVOURITES': FavouritedStatuses,
  22. };
  23. @component => injectIntl(component, { withRef: true })
  24. export default class ColumnsArea extends ImmutablePureComponent {
  25. static contextTypes = {
  26. router: PropTypes.object.isRequired,
  27. };
  28. static propTypes = {
  29. intl: PropTypes.object.isRequired,
  30. columns: ImmutablePropTypes.list.isRequired,
  31. singleColumn: PropTypes.bool,
  32. children: PropTypes.node,
  33. };
  34. state = {
  35. shouldAnimate: false,
  36. }
  37. componentWillReceiveProps() {
  38. this.setState({ shouldAnimate: false });
  39. }
  40. componentDidMount() {
  41. this.node.addEventListener('wheel', this.handleWheel, detectPassiveEvents ? { passive: true } : false);
  42. this.lastIndex = getIndex(this.context.router.history.location.pathname);
  43. this.setState({ shouldAnimate: true });
  44. }
  45. componentDidUpdate() {
  46. this.lastIndex = getIndex(this.context.router.history.location.pathname);
  47. this.setState({ shouldAnimate: true });
  48. }
  49. componentWillUnmount () {
  50. this.node.removeEventListener('wheel', this.handleWheel);
  51. }
  52. handleChildrenContentChange() {
  53. if (!this.props.singleColumn) {
  54. scrollRight(this.node, this.node.scrollWidth - window.innerWidth);
  55. }
  56. }
  57. handleSwipe = (index) => {
  58. this.pendingIndex = index;
  59. const nextLinkTranslationId = links[index].props['data-preview-title-id'];
  60. const currentLinkSelector = '.tabs-bar__link.active';
  61. const nextLinkSelector = `.tabs-bar__link[data-preview-title-id="${nextLinkTranslationId}"]`;
  62. // HACK: Remove the active class from the current link and set it to the next one
  63. // React-router does this for us, but too late, feeling laggy.
  64. document.querySelector(currentLinkSelector).classList.remove('active');
  65. document.querySelector(nextLinkSelector).classList.add('active');
  66. }
  67. handleAnimationEnd = () => {
  68. if (typeof this.pendingIndex === 'number') {
  69. this.context.router.history.push(getLink(this.pendingIndex));
  70. this.pendingIndex = null;
  71. }
  72. }
  73. handleWheel = () => {
  74. if (typeof this._interruptScrollAnimation !== 'function') {
  75. return;
  76. }
  77. this._interruptScrollAnimation();
  78. }
  79. setRef = (node) => {
  80. this.node = node;
  81. }
  82. renderView = (link, index) => {
  83. const columnIndex = getIndex(this.context.router.history.location.pathname);
  84. const title = this.props.intl.formatMessage({ id: link.props['data-preview-title-id'] });
  85. const icon = link.props['data-preview-icon'];
  86. const view = (index === columnIndex) ?
  87. React.cloneElement(this.props.children) :
  88. <ColumnLoading title={title} icon={icon} />;
  89. return (
  90. <div className='columns-area' key={index}>
  91. {view}
  92. </div>
  93. );
  94. }
  95. renderLoading = () => {
  96. return <ColumnLoading />;
  97. }
  98. renderError = (props) => {
  99. return <BundleColumnError {...props} />;
  100. }
  101. render () {
  102. const { columns, children, singleColumn } = this.props;
  103. const { shouldAnimate } = this.state;
  104. const columnIndex = getIndex(this.context.router.history.location.pathname);
  105. this.pendingIndex = null;
  106. if (singleColumn) {
  107. return columnIndex !== -1 ? (
  108. <ReactSwipeableViews index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }}>
  109. {links.map(this.renderView)}
  110. </ReactSwipeableViews>
  111. ) : <div className='columns-area'>{children}</div>;
  112. }
  113. return (
  114. <div className='columns-area' ref={this.setRef}>
  115. {columns.map(column => {
  116. const params = column.get('params', null) === null ? null : column.get('params').toJS();
  117. return (
  118. <BundleContainer key={column.get('uuid')} fetchComponent={componentMap[column.get('id')]} loading={this.renderLoading} error={this.renderError}>
  119. {SpecificComponent => <SpecificComponent columnId={column.get('uuid')} params={params} multiColumn />}
  120. </BundleContainer>
  121. );
  122. })}
  123. {React.Children.map(children, child => React.cloneElement(child, { multiColumn: true }))}
  124. </div>
  125. );
  126. }
  127. }