concord-consortium/rigse

View on GitHub
rails/react-components/src/library/components/stem-finder-result.tsx

Summary

Maintainability
F
4 days
Test Coverage
import React from "react";
import Component from "../helpers/component";
import { MakeTeacherEditionLink } from "../helpers/make-teacher-edition-links";
import GradeLevels from "./grade-levels";
import StemFinderResultStandards from "./stem-finder-result-standards";
import RelatedResourceResult from "./related-resource-result";

import css from "./stem-finder-result.scss";

// vars for special treatment of hover and click states on touch-enabled devices
let pageScrolling = false;
let touchInitialized = false;

const StemFinderResult = Component({
  getInitialState () {
    return {
      favorited: this.props.resource.is_favorite,
      hovering: false,
      isOpen: false,
      lightbox: false,
      showTeacherResourcesButton: false,
      showResource: this.props.showResource
    };
  },

  componentDidMount () {
    this.setState({ hasLoaded: true });
    document.body.addEventListener("touchstart", this.handleTouchStart);
    document.body.addEventListener("touchmove", this.handleTouchMove);
    document.body.addEventListener("touchend", this.handleTouchEnd);
  },

  componentWillUnmount () {
    document.body.removeEventListener("touchstart", this.handleTouchStart);
    document.body.removeEventListener("touchmove", this.handleTouchMove);
    document.body.removeEventListener("touchend", this.handleTouchEnd);
  },

  handleTouchStart (e: any) {
    e.stopPropagation();
    touchInitialized = true;
    pageScrolling = false;
  },

  handleTouchMove (e: any) {
    e.stopPropagation();
    touchInitialized = true;
    pageScrolling = true;
  },

  handleTouchEnd (e: any) {
    e.stopPropagation();
  },

  handleMouseOver (e: any) {
    if (this.state.lightbox) {
      return;
    }
    if (touchInitialized === false && pageScrolling === false) {
      this.setState({ hovering: true });
    }
  },

  handleMouseOut () {
    if (this.state.lightbox) {
      return;
    }
    this.setState({ hovering: false });
  },

  toggleFavorite (e: any) {
    e.preventDefault();
    e.stopPropagation();

    if (!Portal.currentUser.isLoggedIn || !Portal.currentUser.isTeacher) {
      const mouseX = e.pageX + 31;
      const mouseY = e.pageY - 23;
      jQuery("body").append('<div class="portal-pages-favorite-tooltip">Log in or sign up to save resources for quick access!</div>');
      jQuery(".portal-pages-favorite-tooltip").css({ "left": mouseX + "px", "top": mouseY + "px" }).fadeIn("fast");

      window.setTimeout(function () {
        jQuery(".portal-pages-favorite-tooltip").fadeOut("slow", function () { jQuery(this).remove(); });
      }, 3000);
      return;
    }

    const resource = this.props.resource;
    const done = function () {
      resource.is_favorite = !resource.is_favorite;
      this.setState({ favorited: resource.is_favorite });
    }.bind(this);
    if (resource.is_favorite) {
      jQuery.post("/api/v1/materials/remove_favorite", { favorite_id: resource.favorite_id }, done);
      gtag("event", "click", {
        "category": "Favorite Button",
        "resource": `${resource.name} removed from favorites`
      });
    } else {
      jQuery.post("/api/v1/materials/add_favorite", { id: resource.id, material_type: resource.class_name_underscored }, done);
      gtag("event", "click", {
        "category": "Favorite Button",
        "resource": `${resource.name} added to favorites`
      });
    }
  },

  renderFavoriteStar () {
    const active = this.state.favorited ? css.finderResultFavoriteActive : "";
    const divClass = css.finderResultFavorite + " " + active;
    return (
      <div className={divClass} onClick={this.toggleFavorite}>
        <i className={"icon-favorite"} />
      </div>
    );
  },

  // This function no longer called. Leaving here in case we want to restore call with different return value.
  renderTimeRequired () {
    const resource = this.props.resource;
    const timeRequired = resource.material_type === "Activity"
      ? "45 Minutes"
      : resource.material_type === "Investigation"
        ? "2 Weeks"
        : resource.material_type === "Interactive"
          ? "Varies"
          : null;

    if (timeRequired === null) {
      return;
    }
    return (
      <div className={`${css.metaTag} ${css.timeRequired}`}>
        { timeRequired }
      </div>
    );
  },

  renderTags () {
    const resource = this.props.resource;

    // show the private badge only for private community resources
    if (!resource.is_official && (resource.publication_status === "private")) {
      return (
        <div className={`${css.metaTag} ${css.private}`}>
          Private
        </div>
      );
    }
    // show the community badge only for public community resources
    if (!resource.is_official && (resource.publication_status === "published")) {
      return (
        <div className={`${css.metaTag} ${css.community}`}>
          Community
        </div>
      );
    }
  },

  renderAssignedClasses () {
    const { resource } = this.props;
    if (resource.assigned_classes.length < 1) {
      return;
    }
    const assignedClasses = resource.assigned_classes.join(", ");
    return (
      <div className={css.assignedTo}>
        Assigned to { assignedClasses }
      </div>
    );
  },

  handlePreviewClick (e: any) {
    const { resource } = this.props;
    gtag("event", "click", {
      "category": "Resource Preview Button",
      "resource": resource.name
    });
  },

  handleViewCollectionClick (e: any) {
    const { resource } = this.props;
    gtag("event", "click", {
      "category": "Resource View Collection Button",
      "resource": resource.name
    });
  },

  handleTeacherEditionClick (e: any) {
    const { resource } = this.props;
    gtag("event", "click", {
      "category": "Resource Teacher Edition Button",
      "resource": resource.name
    });
  },

  handleTeacherResourcesClick (e: any) {
    const { resource } = this.props;
    gtag("event", "click", {
      "category": "Resource Teacher Resources Button",
      "resource": resource.name
    });
  },

  handleRubricDocClick (e: any) {
    const { resource } = this.props;
    gtag("event", "click", {
      "category": "Resource Rubric Doc Button",
      "resource": resource.name
    });
  },

  handleAssignClick (e: any) {
    const { resource } = this.props;
    gtag("event", "click", {
      "category": "Assign to Class Button",
      "resource": resource.name
    });
  },

  handleTeacherGuideClick (e: any) {
    const { resource } = this.props;
    gtag("event", "click", {
      "category": "Teacher Guide Link",
      "resource": resource.name
    });
  },

  handleAddToCollectionClick (e: any) {
    const { resource } = this.props;
    gtag("event", "click", {
      "category": "Add to Collection Button",
      "resource": resource.name
    });
  },

  renderLinks () {
    const { resource } = this.props;
    const isCollection = resource.material_type === "Collection";
    const isAssignWrapped = window.self !== window.top &&
      window.self.location.hostname === window.top?.location.hostname;
    const assignHandler = resource.links.assign_material && isAssignWrapped
      ? `javascript: window.parent.${resource.links.assign_material.onclick}`
      : resource.links.assign_material
        ? `javascript: ${resource.links.assign_material.onclick}`
        : null;
    const assignLink = resource.links.assign_material && !isCollection
      ? <a href={assignHandler || ""} onClick={this.handleAssignClick}>{ resource.links.assign_material.text }</a>
      : null;
    const editLinkUrl = resource.lara_activity_or_sequence && resource.links.external_lara_edit
      ? resource.links.external_lara_edit.url
      : resource.links.external_edit
        ? resource.links.external_edit.url
        : null;
    const editLink = editLinkUrl
      ? <a href={editLinkUrl} target="_blank" rel="noopener noreferrer">Edit</a>
      : null;
    const copyLink = resource.links.external_copy && !isCollection
      ? <a href={resource.links.external_copy.url} target="_blank" rel="noopener noreferrer">{ resource.links.external_copy.text }</a>
      : null;
    // const printLink = resource.links.print_url && !isCollection
    //   ? <a href={resource.links.print_url.url} target='_blank' rel='noopener'>{resource.links.print_url.text}</a>
    //   : null
    const teacherEditionLink = resource.has_teacher_edition && Portal.currentUser.isTeacher
      ? <a href={MakeTeacherEditionLink(resource.external_url)} target="_blank" rel="noopener noreferrer" onClick={this.handleTeacherEditionClick}>Teacher Edition</a>
      : null;
    const teacherGuideLink = resource.links.teacher_guide && Portal.currentUser.isTeacher
      ? <a href={resource.links.teacher_guide.url} target="_blank" rel="noopener noreferrer" onClick={this.handleTeacherGuideClick}>{ resource.links.teacher_guide.text }</a>
      : null;
    const teacherResourcesLink = resource.links.teacher_resources && Portal.currentUser.isTeacher
      ? <a href={resource.links.teacher_resources.url} target="_blank" rel="noopener noreferrer" onClick={this.handleTeacherResourcesClick}>{ resource.links.teacher_resources.text }</a>
      : null;
    const rubricDocLink = resource.links.rubric_doc && Portal.currentUser.isTeacher
      ? <a href={resource.links.rubric_doc.url} target="_blank" rel="noopener noreferrer" onClick={this.handleRubricDocClick}>{ resource.links.rubric_doc.text }</a>
      : null;
    const assignCollectionLink = resource.links.assign_collection && (Portal.currentUser.isAdmin || Portal.currentUser.isManager)
      ? <a href={resource.links.assign_collection.url} target="_blank" onClick={this.handleAddToCollectionClick} rel="noreferrer">{ resource.links.assign_collection.text }</a>
      : null;
    const portalSettingsLink = resource.links.edit && (Portal.currentUser.isAdmin || Portal.currentUser.isManager)
      ? <a href={resource.links.edit.url} target="_blank" rel="noreferrer">Settings</a>
      : null;

    return (
      <>
        { assignLink }
        { teacherEditionLink }
        { rubricDocLink }
        { teacherGuideLink }
        { teacherResourcesLink }
        { editLink }
        { copyLink }
        { assignCollectionLink }
        { portalSettingsLink }
      </>
    );
  },

  hasStandards () {
    const { resource } = this.props;
    return resource.standard_statements.length > 0;
  },

  renderStandards () {
    const { resource } = this.props;
    return (
      <div className={`${css.collapsible} ${css.finderResultStandards}`}>
        <h2 onClick={this.toggleCollapsible} className={css.collapsibleHeading}>Standards</h2>
        <div className={css.collapsibleBody}>
          <StemFinderResultStandards standardStatements={resource.standard_statements} />
        </div>
      </div>
    );
  },

  renderMoreToggle () {
    const { resource } = this.props;
    const needsMoreToggle = resource.filteredShortDescription.length > 210 || this.hasStandards();

    if (!needsMoreToggle) {
      return (null);
    }

    return (
      <>
        <a href="#" className={css.moreLink} onClick={this.toggleResource}>More</a>
        <a href="#" className={css.lessLink} onClick={this.toggleResource}>Less</a>
      </>
    );
  },

  renderRelatedResources (e: any) {
    const resource = this.props.resource;
    if (resource.related_materials.length === 0 || resource.material_type === "Collection") {
      return null;
    }

    const relatedResources = resource.related_materials.map((_resource: any, i: any) => {
      if (i < 2) {
        return RelatedResourceResult({ key: i, resource: _resource, replaceResource: this.replaceResource });
      }
    });

    return (
      <div className={css.collapsible}>
        <h2 onClick={this.toggleCollapsible} className={css.collapsibleHeading}>Related Activities</h2>
        { relatedResources }
      </div>
    );
  },

  toggleResource (e: any) {
    e.preventDefault();
    this.setState({ isOpen: !this.state.isOpen });
  },

  toggleCollapsible (e: any) {
    jQuery(e.currentTarget).parent().toggleClass(css.collapsibleOpen);
  },

  render () {
    const { resource, index } = this.props;
    const resourceTypeClass = resource.material_type.toLowerCase();
    const finderResultClasses = this.state.isOpen ? `resourceItem ${css.finderResult} ${css.open} ${css[resourceTypeClass]}` : `resourceItem ${css.finderResult} ${css[resourceTypeClass]}`;
    const resourceName = resource.name;
    const resourceLink = resource.stem_resource_url;
    const shortDesc = resource.filteredShortDescription;
    const projectName = resource.projects[0] ? resource.projects[0].name : null;
    const projectNameRegex = / |-|\./g;
    const projectClass = projectName ? projectName.replace(projectNameRegex, "").toLowerCase() : null;
    const transitionDelay = 100 * index;

    return (
      <div className={finderResultClasses} style={{ transitionDelay: transitionDelay + "ms" }}>
        <div className={css.finderResultImagePreview}>
          <img alt={resource.name} src={resource.icon.url} />
        </div>
        <div className={css.finderResultText}>
          <div className={css.finderResultTextName}>
            <a href={resourceLink} target="_blank" title={resourceName} rel="noreferrer">{ resourceName }</a>
          </div>
          <div className={css.metaTags}>
            <GradeLevels resource={resource} />
            { this.renderTags() }
            { this.renderAssignedClasses() }
          </div>
          <div className={css.finderResultTextDescription}>
            { shortDesc }
          </div>
        </div>
        <div className={css.previewLink}>
          { resource.material_type !== "Collection"
            ? <a className={css.previewLinkButton} href={resource.links.preview.url} target="_blank" onClick={this.handlePreviewClick} rel="noreferrer">{ resource.links.preview.text }</a>
            : <a className={css.previewLinkButton} href={resource.links.preview.url} target="_blank" onClick={this.handleViewCollectionClick} rel="noreferrer">View Collection</a>
          }
          { resource.material_type !== "Collection" &&
            <div className={`${css.projectLabel} ${css[projectClass]}`}>
              { projectName }
            </div>
          }
        </div>
        { this.hasStandards() && this.renderStandards() }
        { this.renderRelatedResources() }
        <div className={css.finderResultLinks}>
          { this.renderLinks() }
          { this.renderMoreToggle() }
        </div>
        { this.renderFavoriteStar() }
      </div>
    );
  }
});

export default StemFinderResult;