concord-consortium/lara

View on GitHub
lara-typescript/src/section-authoring/components/page-nav-menu/page-nav-menu.tsx

Summary

Maintainability
C
1 day
Test Coverage
import * as React from "react";
import classNames from "classnames";
import { PageCopyDialog } from "./page-copy-dialog";
import { Previous } from "../../../shared/components/icons/previous-icon";
import { Home } from "../../../shared/components/icons/home-icon";
import { Next } from "../../../shared/components/icons/next-icon";
import { HiddenIcon } from "../../../shared/components/icons/hidden-icon";
import { Completion } from "../../../shared/components/icons/completion-icon";
import { Add } from "../../../shared/components/icons/add-icon";
import { Copy } from "../../../shared/components/icons/copy-icon";
import { IPage, PageId } from "../../api/api-types";

import "./page-nav-menu.scss";

const kMaxPageNavigationButtons = 11;

export interface IPageNavMenuProps {
  pages: IPage[];
  currentPageId: PageId | null;
  copyingPage: boolean;
  setCurrentPageId: (id: PageId) => void;
  addPage: () => void;
  copyPage: (destIndex: number) => void;
  deletePage: () => void;
  showCopyDialog: () => void;
  hideCopyDialog: () => void;
}

export const PageNavMenu: React.FC<IPageNavMenuProps> = ({
  pages,
  currentPageId,
  copyingPage,
  addPage,
  copyPage,
  setCurrentPageId,
  deletePage,
  showCopyDialog,
  hideCopyDialog
  }: IPageNavMenuProps) => {

  const currentPageIndex = pages.findIndex(p => p.id === currentPageId);
  const currentPage = pages[currentPageIndex];
  const [pageChangeInProgress, setPageChangeInProcess] = React.useState(false);

  const handleNavButtonClick = (pageNum: number | null) => {
    if (pageNum == null) {
      handleSelectHomeButton();
    } else {
      const outsideIndex = pageNum < 0 || pageNum + 1 > pages.length;
      if (!outsideIndex) {
        setCurrentPageId(pages[pageNum].id);
      }
    }
  };

  const handleAddPageButtonClick = () => {
    addPage();
  };

  const handleCopyPageButtonClick = () => {
    showCopyDialog();
  };

  const handleSelectHomeButton = () => {
    const activityPath = window.location.pathname.split("/pages")[0];
    window.location.href = activityPath + "/edit";
  };

  const prevPage = currentPageIndex && currentPageIndex > 0 ? currentPageIndex - 1 : null;
  const nextPage = currentPageIndex === null
                     ? 0
                     : currentPageIndex < pages.length - 1
                       ? currentPageIndex + 1 : pages.length - 1;
  const currentPageIsCopyable = currentPageIndex !== null && !currentPage?.isCompletion;
  const prevPageClassName = `page-button ${currentPageIndex === null ? "disabled " : ""}`;
  const prevClickHandler = () => handleNavButtonClick(prevPage);
  const nextPageClassName = `page-button ${currentPageIndex === pages.length - 1 ? "disabled" : ""}`;
  const nextClickHandler = () => handleNavButtonClick(nextPage);
  const copyPageClassName = `page-button ${!currentPageIsCopyable ? "disabled" : ""}`;
  const copyClickHandler = currentPageIsCopyable ? handleCopyPageButtonClick : undefined;

  const renderPreviousButton = () => {
    return (
      <button
        className={`page-button ${pageChangeInProgress || currentPageIndex === null ? "disabled" : ""}`}
        onClick={prevClickHandler}
        aria-label="Previous page"
      >
        <Previous height="24" width="24"/>
      </button>
    );
  };
  const renderNextButton = () => {
    const totalPages = pages.length;
    return (
      <button
        className={`page-button ${pageChangeInProgress || currentPageIndex + 1 === totalPages ? "disabled" : ""}`}
        onClick={nextClickHandler}
        aria-label="Next page"
      >
        <Next height="24" width="24"/>
      </button>
    );
  };

  const renderHomePageButton = () => {
    const currentClass = currentPageIndex === null ? "current" : "";
    return (
      <button
        className={`page-button ${currentClass} ${(pageChangeInProgress) ? "disabled" : ""}`}
        onClick={handleSelectHomeButton}
        aria-label="Home">
        <Home
          className={`icon ${currentClass}`}
          width="24"
          height="24"
        />
      </button>
    );
  };

  const renderPageButtons = () => {
    const totalPages = pages.length;
    const maxPagesLeftOfCurrent = currentPageIndex - 1;
    const maxPagesRightOfCurrent = totalPages - currentPageIndex;
    let minPage = 1;
    let maxPage = totalPages;
    const maxButtonsPerSide = Math.floor(kMaxPageNavigationButtons / 2);
    if (maxPagesLeftOfCurrent < maxButtonsPerSide) {
      maxPage =
        Math.min(totalPages, currentPageIndex + maxButtonsPerSide + (maxButtonsPerSide - maxPagesLeftOfCurrent));
    } else if (maxPagesRightOfCurrent < maxButtonsPerSide) {
      minPage = Math.max(1, currentPageIndex - maxButtonsPerSide - (maxButtonsPerSide - maxPagesRightOfCurrent));
    } else if (totalPages > kMaxPageNavigationButtons) {
      minPage = currentPageIndex - maxButtonsPerSide;
      maxPage = currentPageIndex + maxButtonsPerSide;
    }

    return (
      pages.map((page: IPage, pageIndex: number, pageArray: IPage[]) => {
        const hiddenPagesBefore = pageArray.filter((p, index) => p.isHidden && index < pageIndex ).length;
        const pageNum = pageIndex + 1;
        const pageLabel = pageNum - hiddenPagesBefore;
        const currentClass = currentPageIndex === pageIndex ? "current" : undefined;
        const pageButtonClasses = classNames("page-button", {
          "completion-page-button": page.isCompletion,
          "current": currentClass,
          "disabled": pageChangeInProgress,
          "hidden-page-button": page.isHidden
        });
        const buttonContent = page.isHidden
                              ? <HiddenIcon width="28" height="28" />
                              : page.isCompletion
                                ? <Completion width="28" height="28" />
                                : pageLabel;
        const clickHandler = () => handleNavButtonClick(pageIndex);

        return (
          pageNum >= minPage && pageNum <= maxPage
            ? <button
                className={pageButtonClasses}
                onClick={clickHandler}
                key={`page ${pageNum}`}
                data-cy={`${page.isCompletion ? "nav-pages-completion-page-button" : "nav-pages-button"}`}
                aria-label={`Page ${pageNum}`}
              >
                {buttonContent}
              </button>
            : ""
        );
      })
    );
  };

  const renderAddPageButton = () => {
    return (
      <button
        className="page-button"
        aria-label="Add Page"
        onClick={handleAddPageButtonClick}>
        <Add height="24" width="24" />
      </button>
    );
  };

  const renderCopyPageButton = () => {
    return (
      <button
        className={copyPageClassName}
        aria-label="Copy Page"
        onClick={copyClickHandler}>
        <Copy height="24" width="24" />
      </button>
    );
  };

  return (
    <>
      <nav className="activity-nav" data-cy="activity-nav">
        <div className="nav-pages" data-cy="nav-pages">
          {renderPreviousButton()}
          {renderHomePageButton()}
          {renderPageButtons()}
          {renderNextButton()}
          {renderAddPageButton()}
          {renderCopyPageButton()}
        </div>
      </nav>
      { copyingPage &&
        <PageCopyDialog
          pageId={"1"}
          pages={pages}
          currentPageIndex={currentPageIndex}
          copyPageFunction={copyPage}
          closeDialogFunction={hideCopyDialog} />}
    </>
  );
};