WikiEducationFoundation/WikiEduDashboard

View on GitHub
app/assets/javascripts/components/timeline/week.jsx

Summary

Maintainability
B
5 hrs
Test Coverage
A
93%
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import Block from './block.jsx';
import DateCalculator from '../../utils/date_calculator.js';
import SpringBlock from './SpringBlock';
import BlockList from './BlockList';

const Week = createReactClass({
  displayName: 'Week',
  propTypes: {
    week: PropTypes.object,
    index: PropTypes.number,
    timeline_start: PropTypes.string,
    timeline_end: PropTypes.string,
    meetings: PropTypes.array,
    blocks: PropTypes.array,
    edit_permissions: PropTypes.bool,
    editableBlockIds: PropTypes.array,
    reorderable: PropTypes.bool,
    editableTitles: PropTypes.bool,
    onBlockDrag: PropTypes.func,
    onMoveBlockUp: PropTypes.func,
    onMoveBlockDown: PropTypes.func,
    usingCustomTitles: PropTypes.bool,
    updateTitle: PropTypes.func,
    canBlockMoveUp: PropTypes.func,
    canBlockMoveDown: PropTypes.func,
    saveBlockChanges: PropTypes.func,
    cancelBlockEditable: PropTypes.func,
    addBlock: PropTypes.func,
    deleteWeek: PropTypes.func,
    all_training_modules: PropTypes.array,
    weeksBeforeTimeline: PropTypes.number,
    trainingLibrarySlug: PropTypes.string.isRequired,
    current_user: PropTypes.object
  },
  getInitialState() {
    return { focusedBlockId: null, isHover: false };
  },
  componentDidMount() {
    const hash = location.hash.substring(1);
    const weekNo = this.weekNumber();
    if (hash === `week-${weekNo}`) {
      const week = document.getElementsByName(hash)[0];
      week.scrollIntoView();
    }
  },
  handleMouseEnter() {
    this.setState({ isHover: true });
  },
  handleMouseLeave() {
    this.setState({ isHover: false });
  },
  addBlock() {
    this._scrollToAddedBlock();
    return this.props.addBlock(this.props.week.id);
  },
  toggleFocused(blockId) {
    if (this.state.focusedBlockId === blockId) {
      return this.setState({ focusedBlockId: null });
    }
    return this.setState({ focusedBlockId: blockId });
  },
  _scrollToAddedBlock() {
    const wk = document.getElementsByClassName(`week-${this.props.index}`)[0];
    const scrollTop = window.scrollY || document.body.scrollTop;
    const bottom = Math.abs(__guard__(wk, x => x.getBoundingClientRect().bottom));
    const elBottom = (bottom + scrollTop) - 50;
    return window.scrollTo({ top: elBottom, behavior: 'smooth' });
  },
  weekNumber() {
    return this.props.index + this.props.weeksBeforeTimeline;
  },
  render() {
    const dateCalc = new DateCalculator(this.props.timeline_start, this.props.timeline_end, this.props.index, { zeroIndexed: false });
    let weekDatesContent;
    let meetDates;
    if (this.props.meetings && this.props.meetings.length > 0) {
      meetDates = `Meetings: ${this.props.meetings.join(', ')}`;
    }
    if (this.props.meetings) {
      weekDatesContent = `${dateCalc.start()} - ${dateCalc.end()}`;
    } else {
      weekDatesContent = `Week of ${dateCalc.start()} — AFTER TIMELINE END DATE!`;
    }
    const meetDatesDiv = (
      <div className="margin-bottom">
        {meetDates}
      </div>
    );
    let weekTitleContent;
    if (this.props.week.title) {
      weekTitleContent = this.props.week.title;
    } else {
      weekTitleContent = I18n.t('timeline.week_number', { number: this.weekNumber() });
    }
    const weekId = this.props.week.id;
    const weekTitle = this.props.editableTitles ? (
      <input
        className="week-index week-title-input"
        defaultValue={weekTitleContent}
        maxLength={20}
        onChange={event => this.props.updateTitle(weekId, event.target.value)}
      />
    ) : (
      <h2 className="week-index">{weekTitleContent}<span className="week-range"> ({weekDatesContent})</span></h2>
    );

    // FIXME: This mutates redux state.
    // Instead of sorting during render, the blocks should
    // be sorted by order in redux actions or via a selector at the point
    // the data gets pulled into a parent component from the store.
    this.props.blocks.sort((a, b) => a.order - b.order);

    const blocks = this.props.blocks.map((block, i) => {
      // If in reorderable mode
      if (this.props.reorderable) {
        return <SpringBlock block={block} i={i} key={block.id} {...this.props}/>;
      }
      // If not in reorderable mode
      return (
        <Block
          toggleFocused={this.toggleFocused.bind(this, block.id)}
          block={block}
          key={block.id}
          editPermissions={this.props.edit_permissions}
          deleteBlock={this.props.deleteBlock}
          week_index={this.props.index}
          weekStart={dateCalc.startDate()}
          all_training_modules={this.props.all_training_modules}
          editableBlockIds={this.props.editableBlockIds}
          saveBlockChanges={this.props.saveBlockChanges}
          setBlockEditable={this.props.setBlockEditable}
          cancelBlockEditable={this.props.cancelBlockEditable}
          updateBlock={this.props.updateBlock}
          trainingLibrarySlug={this.props.trainingLibrarySlug}
          current_user={this.props.current_user}
        />
      );
    });

    const addBlock = !this.props.reorderable ? (
      <button className="pull-right week__add-block" href="" onClick={this.addBlock}>Add Block <span className="icon-plus-blue" /></button>
    ) : undefined;

    const deleteWeek = !this.props.reorderable && !this.props.week.is_new ? (
      <button onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} className="pull-right week__delete-week" href="" onClick={this.props.deleteWeek}>Delete Week <span className={`${this.state.isHover ? 'icon-trash_can-hover' : 'icon-trash_can'}`}/></button>
    ) : undefined;

    const weekAddDelete = this.props.edit_permissions ? (
      <div className="week__week-add-delete pull-right">
        {addBlock}
        {deleteWeek}
      </div>
    ) : undefined;

    const weekContent = (
      this.props.reorderable
          ? <BlockList blocks={this.props.blocks} week_id={this.props.week.id} moveBlock={this.props.moveBlock} {...this.props}/>
        : (
          <ul className="week__block-list list-unstyled">
            {blocks}
          </ul>
        )
    );

    let weekClassName = `week week-${this.props.index}`;
    if (!this.props.meetings) {
      weekClassName += ' timeline-warning';
    }

    return (
      <li className={weekClassName}>
        <div className="week__week-header">
          {weekAddDelete}
          {weekTitle}
          {meetDatesDiv}
        </div>
        {weekContent}
      </li>
    );
  }
});

export default Week;

function __guard__(value, transform) {
  return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined;
}