tahnik/devRantron

View on GitHub
app/src/js/components/columns/column_topbar.js

Summary

Maintainability
A
45 mins
Test Coverage
/**
 * This is part of the reusable column
 * Top bar has some major functionalities.
 * It controls the filters (top, algo, day, week)
 * It also fetches new item when the column scrolls to end
 */

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { FILTERS, STATE } from '../../consts/types';

class ColumnTopBar extends Component {
  constructor() {
    super();
    this.state = {
      primary: null,
      secondary: null,
      translateY: 0,
    };
  }
  /**
   * Sets the corret filters when the component mounts
   *
   * @memberof ColumnTopBar
   */
  componentWillMount() {
    const { filters, fetchAfterMount } = this.props;

    const firstPri = this.getFilter(filters.PRIMARY);
    this.setState({ primary: firstPri });

    const firstSec = this.getFilter(filters.SECONDARY);
    if (firstSec) {
      this.setState({ secondary: firstSec });
    }
    if (fetchAfterMount) {
      this.fetch(firstPri, firstSec);
    }
  }
  /**
   * When the component mounts, it attaches a event handler for the column
   * which listens to the scroll events.
   *
   * @memberof ColumnTopBar
   */
  componentDidMount() {
    const { divID } = this.props;
    const element = document.getElementById(divID);
    if (element) {
      element.addEventListener('scroll', () => this.handleScroll());
    }
  }
  /**
   * Make sure to remove the event handler
   *
   * @memberof ColumnTopBar
   */
  componentWillUnmount() {
    const { divID } = this.props;
    const element = document.getElementById(divID);
    if (element) {
      element.removeEventListener('scroll', () => this.handleScroll());
    }
  }
  /**
   * Gets the correct primary and secondary filters
   *
   * @param {string} type
   * @returns {string} correct filter
   * @memberof ColumnTopBar
   */
  getFilter(type) {
    const { filters, sort, range } = this.props;
    // Get the options from the given filter type i.e. algo, top and recent
    const options = filters[type];
    if (options) {
      /**
       * sort and range is previously selected options.
       * if they are undefined, it means that the column is being mounted for the first time
       */
      if (typeof sort !== 'undefined' && sort) {
        if (options[sort.toUpperCase()]) {
          /**
           * This means that we've found out previously selected 'sort' in the given filter type.
           * let's return it
           */
          return sort;
        }
      }
      if (typeof range !== 'undefined' && range) {
        if (options[range.toUpperCase()]) {
          /**
           * This means that we've found out previously selected 'range' in the given filter type.
           * let's return it
           */
          return range;
        }
      }

      /**
       * If we have come this far, this means that the column is being mounted for the first time
       * Let's return the first option from the options
       * For example, if it's algo, recent and top, we are going to return algo
       */

      return options[Object.keys(options)[0]];
    }
    return null;
  }
  /**
   * Fetches more items for the column
   * Calls redux actions
   *
   * @param {any} primary primary filter, most of the time it's sort (top, algo)
   * @param {any} secondary secondary filter, commonly range (day, week)
   * @param {boolean} [refresh=false] if the column should be reset
   * @memberof ColumnTopBar
   */
  fetch(primary, secondary, refresh = false) {
    const { id, type, filters } = this.props;
    if (filters.PRIMARY === FILTERS.SORT) {
      this.props.fetch(primary, secondary, id, refresh, type);
    } else {
      this.props.fetch(secondary, primary, id, refresh, type);
    }
  }
  /**
   * Calls fetch when the column scrolls to end
   *
   * @memberof ColumnTopBar
   */
  handleScroll() {
    const { divID, state } = this.props;
    const element = document.getElementById(divID);
    if (
      element.scrollHeight - element.scrollTop < element.clientHeight + 4000
      && state !== STATE.LOADING
    ) {
      this.fetch(this.state.primary, this.state.secondary);
    }
  }
  /**
   * Refreshes(resets) the column
   *
   * @memberof ColumnTopBar
   */
  refresh() {
    this.fetch(this.state.primary, this.state.secondary, true);
  }
  /**
   * Removes the column.
   * Only used in custom column
   *
   * @memberof ColumnTopBar
   */
  remove() {
    this.props.removeColumn(this.props.id);
  }
  /**
   * Click handler for primary filter
   *
   * @param {string} primary primary filter
   * @memberof ColumnTopBar
   */
  handlePri(primary) {
    this.setState({ primary });
    this.fetch(primary, this.state.secondary);
  }
  /**
   * Click handler for secondary filter
   *
   * @param {any} secondary secondary filter
   * @memberof ColumnTopBar
   */
  handleSec(secondary) {
    this.setState({ secondary });
    this.fetch(this.state.primary, secondary);
  }
  /**
   * If the primary filters have some secondary filters
   * hovering over them make the secondary filters visible
   *
   * @param {string} primary primary filter
   * @memberof ColumnTopBar
   */
  handleHover(primary) {
    const { filters } = this.props;
    if (!filters.HAS_SECONDARY) {
      return;
    }
    const hasSecondary = filters.HAS_SECONDARY[primary.toUpperCase()];
    if (primary === this.state.primary && hasSecondary) {
      this.setState({ translateY: -100 });
    }
  }
  /**
   * Hides the secondary filters when user moves mouse from primary filters
   *
   * @memberof ColumnTopBar
   */
  handleHoverLeave() {
    if (this.state.translateY === -100) {
      this.setState({ translateY: 0 });
    }
  }
  render() {
    const { filters, removeColumn, theme } = this.props;
    const primaryFilters = filters[filters.PRIMARY];

    const secondaryFilters = filters[filters.SECONDARY];
    return (
      <div
        className="column_topbar"
        onMouseLeave={() => this.handleHoverLeave()}
        style={{
          width: `${theme.column.width}px`,
          color: theme.item_card.color,
        }}
      >
        <div className="left_navs">
          <div
            className="primary"
            style={{ transform: `translateY(${this.state.translateY}%)` }}
          >
            { Object.keys(primaryFilters).map((key) => {
              const isActive = this.state.primary === primaryFilters[key] ? 'active' : null;
              return (
                <span
                  key={key}
                  className={`${isActive}`}
                  onClick={() => this.handlePri(primaryFilters[key])}
                  onMouseOver={() => this.handleHover(primaryFilters[key])}
                >{primaryFilters[key]}
                </span>
              );
            })}
          </div>
          <div
            className="secondary"
            style={{ transform: `translateY(${this.state.translateY}%)` }}
          >
            { !secondaryFilters ? null :
              Object.keys(secondaryFilters).map((key) => {
                const isActive = this.state.secondary === secondaryFilters[key] ? 'active' : null;
                return (
                  <span
                    key={key}
                    className={`${isActive}`}
                    onClick={() => this.handleSec(secondaryFilters[key])}
                  >{secondaryFilters[key]}
                  </span>
                );
              })}
          </div>
        </div>
        <div className="right_navs">
          <span>
            <i onClick={() => this.refresh()} className="ion-refresh" />
          </span>
          {
            removeColumn ?
              <span>
                <i onClick={() => this.remove()} className="ion-close-round" />
              </span> : null
          }
        </div>
      </div>
    );
  }
}

ColumnTopBar.propTypes = {
  filters: PropTypes.object.isRequired,
  theme: PropTypes.object.isRequired,
  fetch: PropTypes.func.isRequired,
  removeColumn: PropTypes.func,
  divID: PropTypes.string.isRequired,
  sort: PropTypes.string,
  range: PropTypes.string,
  id: PropTypes.string.isRequired,
  fetchAfterMount: PropTypes.bool.isRequired,
  type: PropTypes.string.isRequired,
  state: PropTypes.string.isRequired,
};

export default ColumnTopBar;