cse112-sp20/Team-Potato

View on GitHub
src/components/TabGroup.jsx

Summary

Maintainability
B
6 hrs
Test Coverage
/**
 * @fileOverview This file is for TabGroup object which is used to contain the
 *               Tab components. Here includes TabGroup constructor, starting
 *               focus mode click, and render.
 *
 * @author      David Dai
 * @author      Gary Chew
 * @author      Chau Vu
 * @author      Fernando Vazquez
 * @author      Stephen Cheung
 *
 * @requires    NPM: react, prop-types, react-bootstrap, react-icons
 * @requires    ../styles/TabGroup.css
 * @requires    ./Tab
 */

import React from 'react';
import PropTypes from 'prop-types';
import Card from 'react-bootstrap/Card';
import { RiDeleteBinLine } from 'react-icons/ri';
import { GrEdit } from 'react-icons/gr';
import { v4 as uuid } from 'uuid';
import Tab from './Tab';
import '../styles/TabGroup.css';

/**
 * @description   A class to represent TabGroup components
 * @class
 */
class TabGroup extends React.Component {
  /**
   * @constructor
   *
   * @property  {string} name       the title of the TabGroup
   * @property  {string} trackid    the unique id of the TabGroup to keep track
   * @property  {string}  view      the location of where the TabGroup is shown
   * @property  {Array} tabs        the array of tabs stored under this TabGroup
   * @property  {function}  deleteGroup       Delete the current TabGroup
   * @property  {function}  editGroup         Rename the current TabGroup
   * @property  {function}  displayFocusMode  Display current TabGroup during focue mode
   * @property  {function}  drop              Drop a tab in the target TabGroup
   * @property  {function}  dragOver          Drag the selected tab from the TabGroup
   *
   */
  /* istanbul ignore next */
  constructor(props) {
    super(props);
    TabGroup.propTypes = {
      name: PropTypes.string.isRequired,
      trackid: PropTypes.string.isRequired,
      view: PropTypes.string.isRequired,
      tabs: PropTypes.arrayOf(
        PropTypes.shape({
          title: PropTypes.string.isRequired,
          url: PropTypes.string.isRequired,
          stored: PropTypes.string.isRequired,
          favIconUrl: PropTypes.string,
        })
      ).isRequired,
      deleteGroup: PropTypes.func,
      editGroup: PropTypes.func,
      removeTab: PropTypes.func,
      displayFocusMode: PropTypes.func,
      drop: PropTypes.func,
      dragOver: PropTypes.func,
    };
    /** default function properties definition */
    TabGroup.defaultProps = {
      deleteGroup: () => {},
      editGroup: () => {},
      removeTab: () => {},
      drop: () => {},
      dragOver: () => {},
      displayFocusMode: () => {},
    };
    /** set the current default states
     * @type  {boolean} editMode: decide whether the tabgroup name is changable
     * @type   {string} newName:  the new name to assign to the tabgroup
     * */
    this.state = {
      editMode: false,
      // eslint-disable-next-line react/destructuring-assignment
      newName: this.props.name,
    };
  }

  /**
   * @description:  The display of the current TabGroup once focuemode is launched
   */
  onFocusModeClick = () => {
    const { name, tabs, displayFocusMode } = this.props;
    displayFocusMode(
      name,
      tabs.map((tab) => tab.url)
    );
  };

  /**
   * @description:  Change the state of making the TabGroup editable
   */
  toggleEdit = () => {
    this.setState({ editMode: true });
  };

  /**
   * @description:  How the TabGroup is rendered
   *
   * @returns {*}
   */
  render() {
    /** add the following to the props
     * name: the name of the tabgroup
     * trackid: uuid for each tabgroup
     * deleteGroup: function to delete a tabgroup
     * editGroup: function to rename a tabgroup
     * removeTab: function to remove a tabgroup
     * view: the location of where the tabgroup is shown
     * drop: function to drop a tab into the tabgroup
     * dragOver: function to drag a tab over
     */
    const {
      name,
      trackid,
      tabs,
      deleteGroup,
      editGroup,
      removeTab,
      view,
      drop,
      dragOver,
    } = this.props;
    /** set the following to the state
     *  editMode: whether the name is changable or not
     *  newName: the new name for the tabgroup
     */
    const { editMode, newName } = this.state;
    return (
      <Card
        data-testid="tab-group" /** for unit testing */
        id={trackid}
        onDrop={drop}
        onDragOver={dragOver}
        droppable="true"
      >
        <Card.Header as="h5" droppable="true" id={name}>
          {editMode ? (
            /** if enters editMode */
            <input
              // eslint-disable-next-line jsx-a11y/no-autofocus
              autoFocus
              type="text"
              defaultValue={name}
              onChange={(e) => {
                this.setState({ newName: e.target.value });
              }}
              onKeyPress={(e) => {
                if (e.key === 'Enter') {
                  if (name !== newName) {
                    editGroup(trackid, newName);
                  }
                  /** change state so the page will render */
                  this.setState({ editMode: false });
                }
              }}
            />
          ) : (
            <div id={name} droppable="true">
              <strong className="tabGroupName">{name}</strong>
              {view === 'menu' ? (
                <button
                  /** edit group button */
                  type="button"
                  onClick={this.toggleEdit}
                  data-testid="edit-button"
                >
                  <GrEdit />
                </button>
              ) : null}
              {view === 'menu' ? (
                <div className="buttonGroup">
                  <button
                    /** delete group button */
                    className="deleteButton"
                    type="button"
                    onClick={() => deleteGroup(trackid)}
                    data-testid="delete-button"
                  >
                    <RiDeleteBinLine />
                  </button>
                </div>
              ) : (
                /** view that is not on the menu, which means on the popup */
                <div className="buttonGroup">
                  <button
                    className="focusButton"
                    type="button"
                    data-testid="focus-button"
                    onClick={this.onFocusModeClick}
                  >
                    <img
                      /** this is the start green triangle image source */
                      src="https://icons.iconarchive.com/icons/icons-land/vista-multimedia/256/Play-1-Hot-icon.png"
                      alt="start-button"
                      width="25px"
                      height="25px"
                    />
                  </button>
                </div>
              )}
            </div>
          )}
        </Card.Header>
        {view === 'menu' ? (
          /** inside the menu, where the tabs being drag and droppable in respect to tabgroups */
          <div>
            {tabs.length !== 0 ? (
              <Card.Body id={name} droppable="true">
                {tabs.map((tab) => (
                  <Tab
                    title={tab.title}
                    url={tab.url}
                    favIconUrl={tab.favIconUrl}
                    groupName={name}
                    removeTab={removeTab}
                    key={uuid()}
                    stored={trackid}
                  />
                ))}
              </Card.Body>
            ) : (
              <Card.Body id={name} droppable="true" className="emptyTabGroup">
                This tab group is empty. Drag a tab here to add.
              </Card.Body>
            )}
          </div>
        ) : null}
      </Card>
    );
  }
}

export default TabGroup;