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.

181 lines
5.7 KiB

  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import ImmutablePropTypes from 'react-immutable-proptypes';
  4. import ImmutablePureComponent from 'react-immutable-pure-component';
  5. import BundleContainer from '../containers/bundle_container';
  6. import ColumnLoading from './column_loading';
  7. import DrawerLoading from './drawer_loading';
  8. import BundleColumnError from './bundle_column_error';
  9. import {
  10. Compose,
  11. Notifications,
  12. HomeTimeline,
  13. CommunityTimeline,
  14. PublicTimeline,
  15. HashtagTimeline,
  16. DirectTimeline,
  17. FavouritedStatuses,
  18. BookmarkedStatuses,
  19. ListTimeline,
  20. Directory,
  21. } from '../../ui/util/async-components';
  22. import ComposePanel from './compose_panel';
  23. import NavigationPanel from './navigation_panel';
  24. import { supportsPassiveEvents } from 'detect-passive-events';
  25. import { scrollRight } from '../../../scroll';
  26. const componentMap = {
  27. 'COMPOSE': Compose,
  28. 'HOME': HomeTimeline,
  29. 'NOTIFICATIONS': Notifications,
  30. 'PUBLIC': PublicTimeline,
  31. 'REMOTE': PublicTimeline,
  32. 'COMMUNITY': CommunityTimeline,
  33. 'HASHTAG': HashtagTimeline,
  34. 'DIRECT': DirectTimeline,
  35. 'FAVOURITES': FavouritedStatuses,
  36. 'BOOKMARKS': BookmarkedStatuses,
  37. 'LIST': ListTimeline,
  38. 'DIRECTORY': Directory,
  39. };
  40. export default class ColumnsArea extends ImmutablePureComponent {
  41. static contextTypes = {
  42. router: PropTypes.object.isRequired,
  43. };
  44. static propTypes = {
  45. columns: ImmutablePropTypes.list.isRequired,
  46. isModalOpen: PropTypes.bool.isRequired,
  47. singleColumn: PropTypes.bool,
  48. children: PropTypes.node,
  49. };
  50. // Corresponds to (max-width: $no-gap-breakpoint + 285px - 1px) in SCSS
  51. mediaQuery = 'matchMedia' in window && window.matchMedia('(max-width: 1174px)');
  52. state = {
  53. renderComposePanel: !(this.mediaQuery && this.mediaQuery.matches),
  54. }
  55. componentDidMount() {
  56. if (!this.props.singleColumn) {
  57. this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false);
  58. }
  59. if (this.mediaQuery) {
  60. if (this.mediaQuery.addEventListener) {
  61. this.mediaQuery.addEventListener('change', this.handleLayoutChange);
  62. } else {
  63. this.mediaQuery.addListener(this.handleLayoutChange);
  64. }
  65. this.setState({ renderComposePanel: !this.mediaQuery.matches });
  66. }
  67. this.isRtlLayout = document.getElementsByTagName('body')[0].classList.contains('rtl');
  68. }
  69. componentWillUpdate(nextProps) {
  70. if (this.props.singleColumn !== nextProps.singleColumn && nextProps.singleColumn) {
  71. this.node.removeEventListener('wheel', this.handleWheel);
  72. }
  73. }
  74. componentDidUpdate(prevProps) {
  75. if (this.props.singleColumn !== prevProps.singleColumn && !this.props.singleColumn) {
  76. this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false);
  77. }
  78. }
  79. componentWillUnmount () {
  80. if (!this.props.singleColumn) {
  81. this.node.removeEventListener('wheel', this.handleWheel);
  82. }
  83. if (this.mediaQuery) {
  84. if (this.mediaQuery.removeEventListener) {
  85. this.mediaQuery.removeEventListener('change', this.handleLayoutChange);
  86. } else {
  87. this.mediaQuery.removeListener(this.handleLayoutChange);
  88. }
  89. }
  90. }
  91. handleChildrenContentChange() {
  92. if (!this.props.singleColumn) {
  93. const modifier = this.isRtlLayout ? -1 : 1;
  94. this._interruptScrollAnimation = scrollRight(this.node, (this.node.scrollWidth - window.innerWidth) * modifier);
  95. }
  96. }
  97. handleLayoutChange = (e) => {
  98. this.setState({ renderComposePanel: !e.matches });
  99. }
  100. handleWheel = () => {
  101. if (typeof this._interruptScrollAnimation !== 'function') {
  102. return;
  103. }
  104. this._interruptScrollAnimation();
  105. }
  106. setRef = (node) => {
  107. this.node = node;
  108. }
  109. renderLoading = columnId => () => {
  110. return columnId === 'COMPOSE' ? <DrawerLoading /> : <ColumnLoading multiColumn />;
  111. }
  112. renderError = (props) => {
  113. return <BundleColumnError multiColumn errorType='network' {...props} />;
  114. }
  115. render () {
  116. const { columns, children, singleColumn, isModalOpen } = this.props;
  117. const { renderComposePanel } = this.state;
  118. if (singleColumn) {
  119. return (
  120. <div className='columns-area__panels'>
  121. <div className='columns-area__panels__pane columns-area__panels__pane--compositional'>
  122. <div className='columns-area__panels__pane__inner'>
  123. {renderComposePanel && <ComposePanel />}
  124. </div>
  125. </div>
  126. <div className='columns-area__panels__main'>
  127. <div className='tabs-bar__wrapper'><div id='tabs-bar__portal' /></div>
  128. <div className='columns-area columns-area--mobile'>{children}</div>
  129. </div>
  130. <div className='columns-area__panels__pane columns-area__panels__pane--start columns-area__panels__pane--navigational'>
  131. <div className='columns-area__panels__pane__inner'>
  132. <NavigationPanel />
  133. </div>
  134. </div>
  135. </div>
  136. );
  137. }
  138. return (
  139. <div className={`columns-area ${ isModalOpen ? 'unscrollable' : '' }`} ref={this.setRef}>
  140. {columns.map(column => {
  141. const params = column.get('params', null) === null ? null : column.get('params').toJS();
  142. const other = params && params.other ? params.other : {};
  143. return (
  144. <BundleContainer key={column.get('uuid')} fetchComponent={componentMap[column.get('id')]} loading={this.renderLoading(column.get('id'))} error={this.renderError}>
  145. {SpecificComponent => <SpecificComponent columnId={column.get('uuid')} params={params} multiColumn {...other} />}
  146. </BundleContainer>
  147. );
  148. })}
  149. {React.Children.map(children, child => React.cloneElement(child, { multiColumn: true }))}
  150. </div>
  151. );
  152. }
  153. }