.ts
TypeScript
(application/typescript)
// 1st-party
import type { ReactIsland } from "@ethicdevs/react-monolith";
// 3rd-party
import React, { useCallback } from "react";
import styled, { css } from "styled-components";
// app
import type {
  RepositoryFile,
  RepositoryLog,
  WithThemeSchemeProp,
} from "../types";
import { buildRouteLink } from "../utils/shared";
import { AppRoute } from "../routes.defs";
import { Const } from "../const";
import { NamedColors } from "../utils/style";
// app components
import { Grid } from "../components/Grid";
import { TextEllipsis } from "../components/TextEllipsis.styled";
import { Select } from "../components/TextInput.styled";
import { FolderIcon } from "../components/icons/FolderIcon";
import { FileIcon } from "../components/icons/FileIcon";
import { GitCommmitIcon } from "../components/icons/GitCommitIcon";
// app islands
// import RepositoryCommitSummaryLine from "./RepositoryCommitSummaryLine";

export interface RepositoryTreeViewProps {
  branches: string[];
  currentPath: string;
  currentRef: string;
  lastCommit: RepositoryLog;
  orgSlug: string;
  repoFiles: RepositoryFile[];
  repoSlug: string;
}

const RepositoryTreeView: ReactIsland<
  RepositoryTreeViewProps & WithThemeSchemeProp
> = ({
  themeScheme,
  branches,
  currentPath,
  currentRef,
  // lastCommit,
  orgSlug,
  repoFiles,
  repoSlug,
}) => {
  const buildRepoFileLink = useCallback(
    (file: RepositoryFile, ref: string = currentRef) => {
      const fileName = `${file.name}${file.type === "tree" ? "/" : ""}`;
      return {
        text: fileName,
        href:
          currentPath === "/"
            ? buildRouteLink(AppRoute.REPOSITORY_BROWSER_WITH_PATH, {
                orgSlug,
                repoSlug,
                currentRef: ref,
                "*": fileName,
              })
            : buildRouteLink(AppRoute.REPOSITORY_BROWSER_WITH_PATH, {
                orgSlug,
                repoSlug,
                currentRef: ref,
                "*": `${
                  currentPath.endsWith("/") || currentPath === ""
                    ? currentPath
                    : `${currentPath}/`
                }${fileName}`,
              }),
      };
    },
    [orgSlug, repoSlug, currentPath, currentRef],
  );

  const currPathParts = currentPath.split("/");
  const shouldShowPrevPath = currentPath !== "/";

  let prevPath: string | null = currPathParts
    .slice(0, currPathParts.length - 2)
    .join("/");
  prevPath = prevPath.trim() === "" ? null : prevPath;
  prevPath = prevPath == null ? "/" : prevPath;

  const prevPathLink =
    prevPath === "/"
      ? buildRouteLink(AppRoute.REPOSITORY_DETAILS, { orgSlug, repoSlug })
      : buildRouteLink(AppRoute.REPOSITORY_BROWSER_WITH_PATH, {
          orgSlug,
          repoSlug,
          currentRef,
          "*": prevPath.endsWith("/") ? prevPath : `${prevPath}/`,
        });

  return (
    <StyledRepositoryTreeViewContainer>
      <Grid.Col fluid>
        <Grid.Row
          gap={8}
          alignItems={"center"}
          justifyContent={"flex-start"}
          style={{
            width: "100%",
            padding: 8,
            borderBottom: `1px solid ${NamedColors.BORDER_DEFAULT[themeScheme]}`,
          }}
        >
          <Grid.Row fluid nowrap alignItems={"center"} gap={8}>
            <Select
              themeScheme={themeScheme}
              title={"Branch"}
              onChange={(e) => {
                console.log("branch changed to: ", e.currentTarget.value);
                window.location.href = buildRouteLink(
                  AppRoute.REPOSITORY_BROWSER_WITH_PATH,
                  {
                    orgSlug: orgSlug,
                    repoSlug: repoSlug,
                    currentRef: e.currentTarget.value,
                    "*": currentPath === "/" ? "" : currentPath,
                  },
                );
                window.location.replace(
                  buildRouteLink(AppRoute.REPOSITORY_BROWSER_WITH_PATH, {
                    orgSlug: orgSlug,
                    repoSlug: repoSlug,
                    currentRef: e.currentTarget.value,
                    "*": currentPath === "/" ? "" : currentPath,
                  }),
                );
                window.location.reload();
              }}
              style={{
                height: 32,
                width: "auto",
                minWidth: 80,
                padding: "0 8px",
              }}
            >
              {branches.map(
                (branch, idx) =>
                  branch.trim() != "" && (
                    <option
                      key={branch}
                      value={branch}
                      selected={
                        branches.findIndex((b) => b === currentRef) === idx ||
                        (currentRef === Const.DEFAULT_HEAD_REF &&
                          branch === Const.PRIMARY_BRANCH_REF)
                          ? true
                          : undefined
                      }
                    >
                      {branch}
                    </option>
                  ),
              )}
            </Select>
            {shouldShowPrevPath && (
              <a
                title={`Go to root`}
                href={buildRouteLink(AppRoute.REPOSITORY_DETAILS, {
                  orgSlug,
                  repoSlug,
                })}
              >
                ~
              </a>
            )}
            {currPathParts.map(
              (pathPart, idx) =>
                pathPart.trim() !== "" &&
                pathPart !== "/" && (
                  <a
                    key={[idx, pathPart].join(":")}
                    title={`Go to "${currPathParts
                      .slice(0, idx + 1)
                      .join("/")}/" folder`}
                    href={buildRouteLink(
                      AppRoute.REPOSITORY_BROWSER_WITH_PATH,
                      {
                        orgSlug,
                        repoSlug,
                        currentRef,
                        "*": `${currPathParts.slice(0, idx + 1).join("/")}/`,
                      },
                    )}
                  >
                    <TextEllipsis>{pathPart}/</TextEllipsis>
                  </a>
                ),
            )}
          </Grid.Row>
          <Grid.Row nowrap alignItems={"center"}>
            <GitCommmitIcon
              color={NamedColors.TEXT_DEFAULT[themeScheme]}
              size={24}
            />
            <a
              style={{ minWidth: "max-content" }}
              href={buildRouteLink(AppRoute.REPOSITORY_COMMITS_LOG, {
                orgSlug,
                repoSlug,
                currentRef,
              })}
            >
              Commits History
            </a>
          </Grid.Row>
        </Grid.Row>
        <Grid.Col fluid nowrap>
          <ul
            style={{
              listStyle: "none",
              margin: "0 0 8px 0",
              padding: 0,
              width: "100%",
            }}
          >
            {shouldShowPrevPath && (
              <StyledTreeViewListItem
                key={"go-previous"}
                themeScheme={themeScheme}
              >
                <StyledTreeViewListItemAnchor
                  href={prevPathLink}
                  title={"Go to previous folder (..)"}
                  fullWidth
                >
                  ..
                </StyledTreeViewListItemAnchor>
              </StyledTreeViewListItem>
            )}
            {repoFiles.map((file) => {
              const fileLink = buildRepoFileLink(file, currentRef);
              return (
                <StyledTreeViewListItem
                  key={[file.id, file.name].join(":")}
                  themeScheme={themeScheme}
                >
                  <StyledTreeViewListItemAnchor
                    style={{ flex: "0 0 30%" }}
                    href={fileLink.href}
                    title={fileLink.text}
                  >
                    {fileLink.text.endsWith("/") ? (
                      <FolderIcon
                        color={NamedColors.TEXT_MUTED[themeScheme]}
                        size={16}
                      />
                    ) : (
                      <FileIcon
                        color={NamedColors.TEXT_MUTED[themeScheme]}
                        size={16}
                      />
                    )}
                    <span style={{ marginLeft: 8 }}>
                      <TextEllipsis>{fileLink.text}</TextEllipsis>
                    </span>
                  </StyledTreeViewListItemAnchor>
                  {file.lastCommit != null && (
                    <StyledTreeViewListItemAnchor
                      style={{ flex: 1, marginLeft: 16 }}
                      href={buildRouteLink(AppRoute.REPOSITORY_SHOW_OBJECT, {
                        orgSlug,
                        repoSlug,
                        objectId: file.lastCommit.commit,
                      })}
                      title={file.lastCommit.subject}
                    >
                      <span
                        style={{ flex: 1, opacity: 0.77, fontWeight: "normal" }}
                      >
                        <TextEllipsis>{file.lastCommit.subject}</TextEllipsis>
                      </span>
                      <span style={{ marginLeft: 16 }}>
                        {file.lastCommit.abbreviated_commit}
                      </span>
                    </StyledTreeViewListItemAnchor>
                  )}
                </StyledTreeViewListItem>
              );
            })}
          </ul>
        </Grid.Col>
      </Grid.Col>
    </StyledRepositoryTreeViewContainer>
  );
};

const StyledTreeViewListItem = styled.li<WithThemeSchemeProp>`
  display: flex;
  flex-flow: row nowrap;
  justify-content: flex-start;
  align-items: center;

  height: 40px;
  width: 100%;

  padding: 0 8px;

  ${({ themeScheme }) => css`
    border-bottom: 1px solid ${NamedColors.CARD_ALPHA_01[themeScheme]};

    &:hover {
      background-color: ${NamedColors.CARD_OVERLAY[themeScheme]};
    }
  `}
`;

const StyledTreeViewListItemAnchor = styled.a<{ fullWidth?: boolean }>`
  display: flex;
  flex-flow: row nowrap;
  justify-content: flex-start;
  align-items: center;

  ${({ fullWidth = false }) =>
    fullWidth === true &&
    css`
      width: 100%;
    `}
`;

const StyledRepositoryTreeViewContainer = styled.div`
  width: 100%;
`;

RepositoryTreeView.displayName = "RepositoryTreeView";
export default RepositoryTreeView;

GitFOSS • v0.2.0 (#421408f) • MIT License