Skip to content

Commit

Permalink
Merge pull request #26 from AElfProject/feature/github-import-selection
Browse files Browse the repository at this point in the history
Feature/GitHub import selection
  • Loading branch information
yongenaelf authored Aug 8, 2024
2 parents 8e813b6 + 6e45159 commit 667599b
Show file tree
Hide file tree
Showing 23 changed files with 2,024 additions and 2 deletions.
56 changes: 56 additions & 0 deletions app/api/get-repo-blobs/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { octokit } from "@/components/github/octokit";
import { getRepoBlobsSchema } from "@/components/github/schema";

import { z } from "zod";

const requestSchema = z.object({
owner: z.string(),
repo: z.string(),
branch: z.string(),
paths: z.array(z.string()),
});

export async function GET(request: Request) {
const { searchParams } = new URL(request.url);

const { owner, repo, branch, paths } = requestSchema.parse({
owner: searchParams.get("owner"),
repo: searchParams.get("repo"),
branch: searchParams.get("branch"),
paths: searchParams.getAll("path"),
});

const {
data: { tree },
} = await octokit.rest.git.getTree({
owner,
repo,
tree_sha: branch,
recursive: "true",
});

let response: { path: string; contents: string }[] = [];

const files = tree
.filter((i) => i.type === "blob")
.filter((i) => (i.path ? paths.includes(i.path) : false));

for (const file of files) {
const {
data: { content },
} = await octokit.rest.git.getBlob({
owner,
repo,
file_sha: file.sha!,
});

response.push({
path: file.path!,
contents: Buffer.from(content, "base64").toString("ascii"),
});
}

const parsed = getRepoBlobsSchema.parse(response);

return Response.json(parsed);
}
21 changes: 21 additions & 0 deletions app/api/get-repo-branches/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { octokit } from "@/components/github/octokit";
import { getRepoBranchesSchema } from "@/components/github/schema";

import { z } from "zod";

const requestSchema = z.object({ owner: z.string(), repo: z.string() });

export async function GET(request: Request) {
const { searchParams } = new URL(request.url);

const { owner, repo } = requestSchema.parse({
owner: searchParams.get("owner"),
repo: searchParams.get("repo"),
});

const { data } = await octokit.rest.repos.listBranches({ owner, repo });

const parsed = getRepoBranchesSchema.parse(data.map((i) => i.name));

return Response.json(parsed);
}
21 changes: 21 additions & 0 deletions app/api/get-repo-info/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { octokit } from "@/components/github/octokit";
import { getRepoInfoSchema } from "@/components/github/schema";

import { z } from "zod";

const requestSchema = z.object({ owner: z.string(), repo: z.string() });

export async function GET(request: Request) {
const { searchParams } = new URL(request.url);

const { owner, repo } = requestSchema.parse({
owner: searchParams.get("owner"),
repo: searchParams.get("repo"),
});

const { data } = await octokit.rest.repos.get({ owner, repo });

const parsed = getRepoInfoSchema.parse(data);

return Response.json(parsed);
}
40 changes: 40 additions & 0 deletions app/api/get-repo-tree/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { octokit } from "@/components/github/octokit";
import { getRepoTreeSchema } from "@/components/github/schema";

import { z } from "zod";

const requestSchema = z.object({
owner: z.string(),
repo: z.string(),
branch: z.string(),
});

export async function GET(request: Request) {
const { searchParams } = new URL(request.url);

const { owner, repo, branch } = requestSchema.parse({
owner: searchParams.get("owner"),
repo: searchParams.get("repo"),
branch: searchParams.get("branch"),
});

const {
data: { default_branch },
} = await octokit.rest.repos.get({
owner,
repo,
});

const {
data: { tree },
} = await octokit.rest.git.getTree({
owner,
repo,
tree_sha: branch || default_branch,
recursive: "true",
});

const parsed = getRepoTreeSchema.parse(tree);

return Response.json(parsed);
}
10 changes: 10 additions & 0 deletions app/import/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import GitHub from "@/components/github";

export default function Page() {
return (
<div className="container px-4 py-12 md:px-6 lg:py-16">
<h1 className="text-2xl mb-2">Import GitHub repository</h1>
<GitHub />
</div>
);
}
8 changes: 6 additions & 2 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,12 @@ export default function Home() {
<HomeCard key={link.title} {...link} />
))}
</div>
<h3 className="text-xl font-bold">... enter a GitHub repo url:</h3>
<RepoUrlForm />
<h3 className="text-xl font-bold">
... enter a{" "}
<Link href="/import" className="hover:underline">
GitHub repo url (click here)
</Link>{" "}
</h3>
<h3 className="text-xl font-bold">... or generate from a prompt:</h3>
<div className="grid w-full grid-cols-1 gap-6 md:grid-cols-2">
<div className="rounded-lg border bg-background p-6 shadow-sm">
Expand Down
63 changes: 63 additions & 0 deletions components/github/file-selection.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
.checkbox {
font-size: 16px;
user-select: none;
padding: 20px;
box-sizing: content-box;
}

.checkbox .tree,
.checkbox .tree-node,
.checkbox .tree-node-group {
list-style: none;
margin: 0;
padding: 0;
}

.checkbox .tree-branch-wrapper,
.checkbox .tree-node__leaf {
outline: none;
}

.checkbox .tree-node {
cursor: pointer;
}

.checkbox .tree-node .name:hover {
background: rgba(0, 0, 0, 0.1);
}

.checkbox .tree-node--focused .name {
background: rgba(0, 0, 0, 0.2);
}

.checkbox .tree-node {
display: inline-block;
}

.checkbox .tree-node > * {
display: inline;
}

.checkbox .checkbox-icon {
margin: 0 5px;
vertical-align: middle;
}

.checkbox button {
border: none;
background: transparent;
cursor: pointer;
}

.checkbox .arrow {
margin-left: 5px;
vertical-align: middle;
}

.checkbox .arrow--open {
transform: rotate(90deg);
}

.checkbox .tree-leaf-list-item {
margin-left: 21px;
}
134 changes: 134 additions & 0 deletions components/github/file-selection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import React, { useMemo } from "react";
import { FaSquare, FaCheckSquare, FaMinusSquare } from "react-icons/fa";
import { IoMdArrowDropright } from "react-icons/io";
import TreeView, { ITreeViewOnSelectProps } from "react-accessible-treeview";
import clsx from "clsx";
import { IconBaseProps } from "react-icons/lib";
import "./file-selection.css";
import { useRepoTree } from "./use-octokit";

function FileSelection({
ownerrepo,
branch,
onSelect,
}: {
ownerrepo?: string;
branch?: string;
onSelect?: (props: ITreeViewOnSelectProps) => void;
}) {
const { data: repoData, isLoading } = useRepoTree(ownerrepo, branch);

const data = useMemo(() => {
if (!!repoData) {
const tree = repoData;

const root = {
id: 0,
children: tree
.filter((i) => !i.path?.includes("/"))
.map((i) => i.path!),
parent: null,
name: "",
};

const rest = tree.map((i) => ({
id: i.path!,
name: i.path?.split("/").pop() || "",
children: tree
.filter((j) => i.path === j.path?.split("/").slice(0, -1).join("/"))
.map((k) => k.path!),
parent:
tree.find((j) => j.path === i.path?.split("/").slice(0, -2).join("/"))
?.path || root.id,
}));

return [root, ...rest];
}

return undefined;
}, [repoData]);

if (isLoading || !data) return <div>Loading...</div>;

return (
<div>
<div className="checkbox">
<TreeView
data={data}
aria-label="Checkbox tree"
multiSelect
propagateSelect
propagateSelectUpwards
togglableSelect
onSelect={onSelect}
nodeRenderer={({
element,
isBranch,
isExpanded,
isSelected,
isHalfSelected,
getNodeProps,
level,
handleSelect,
handleExpand,
}) => {
return (
<div
{...getNodeProps({ onClick: handleExpand })}
style={{ marginLeft: 40 * (level - 1) }}
>
{isBranch && <ArrowIcon isOpen={isExpanded} />}
<CheckBoxIcon
className="checkbox-icon"
onClick={(e) => {
handleSelect(e);
e.stopPropagation();
}}
variant={
isHalfSelected ? "some" : isSelected ? "all" : "none"
}
/>
<span className="name">{element.name}</span>
</div>
);
}}
/>
</div>
</div>
);
}

const ArrowIcon = ({
isOpen,
className,
}: {
isOpen: boolean;
className?: string;
}) => {
const baseClass = "arrow";
const classes = clsx(
baseClass,
{ [`${baseClass}--closed`]: !isOpen },
{ [`${baseClass}--open`]: isOpen },
className
);
return <IoMdArrowDropright className={classes} />;
};

const CheckBoxIcon = ({
variant,
...rest
}: IconBaseProps & { variant: "all" | "none" | "some" | string }) => {
switch (variant) {
case "all":
return <FaCheckSquare {...rest} />;
case "none":
return <FaSquare {...rest} />;
case "some":
return <FaMinusSquare {...rest} />;
default:
return null;
}
};

export default FileSelection;
Loading

0 comments on commit 667599b

Please sign in to comment.