import type { ReactIsland } from "@ethicdevs/react-monolith";
import React, { useCallback } from "react";
import styled, { css } from "styled-components";
import type {
RepositoryFile,
RepositoryLog,
WithThemeSchemeProp,
} from "../types";
import { buildRouteLink } from "../utils/shared";
import { AppRoute } from "../routes.defs";
import { Const } from "../const";
import { Grid } from "../components/Grid";
import { TextEllipsis } from "../components/TextEllipsis.styled";
import { NamedColors } from "../utils/style";
import { Select } from "../components/TextInput.styled";
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,
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.__router.push(
buildRouteLink(AppRoute.REPOSITORY_BROWSER_WITH_PATH, {
orgSlug: orgSlug,
repoSlug: repoSlug,
currentRef: e.currentTarget.value,
"*": currentPath === "/" ? "" : currentPath,
}),
);
}}
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"}>
<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}
>
<span>
<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: 32px;
width: 100%;
padding: 0 8px;
${({ themeScheme }) => css`
border-bottom: 1px solid ${NamedColors.BORDER_DEFAULT[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;