Skip to content

Commit

Permalink
Add repo carousel and "how to search" section to homepage (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
brendan-kellam authored Sep 19, 2024
1 parent 71717ac commit 1c756d2
Show file tree
Hide file tree
Showing 8 changed files with 559 additions and 5 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
"class-variance-authority": "^0.7.0",
"client-only": "^0.0.1",
"clsx": "^2.1.1",
"embla-carousel-auto-scroll": "^8.3.0",
"embla-carousel-react": "^8.3.0",
"escape-string-regexp": "^5.0.0",
"http-status-codes": "^2.3.0",
"lucide-react": "^0.435.0",
Expand Down
2 changes: 2 additions & 0 deletions src/app/navigationMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@ export const NavigationMenu = () => {
src={logoDark}
className="h-11 w-auto hidden dark:block"
alt={"Sourcebot logo"}
priority={true}
/>
<Image
src={logoLight}
className="h-11 w-auto block dark:hidden"
alt={"Sourcebot logo"}
priority={true}
/>
</div>

Expand Down
155 changes: 152 additions & 3 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,183 @@
import { listRepositories } from "@/lib/server/searchService";
import { isServiceError } from "@/lib/utils";
import Image from "next/image";
import { Suspense } from "react";
import logoDark from "../../public/sb_logo_dark_large.png";
import logoLight from "../../public/sb_logo_light_large.png";
import { NavigationMenu } from "./navigationMenu";
import { RepositoryCarousel } from "./repositoryCarousel";
import { SearchBar } from "./searchBar";
import { Separator } from "@/components/ui/separator";

export default function Home() {

export default async function Home() {
return (
<div className="h-screen flex flex-col items-center">
{/* TopBar */}
<NavigationMenu />
<div className="flex flex-col justify-center items-center p-4 mt-48">

<div className="flex flex-col justify-center items-center mt-8 md:mt-32 max-w-[90%]">
<div className="max-h-44 w-auto">
<Image
src={logoDark}
className="w-full h-full hidden dark:block"
alt={"Sourcebot logo"}
priority={true}
/>
<Image
src={logoLight}
className="w-full h-full block dark:hidden"
alt={"Sourcebot logo"}
priority={true}
/>
</div>
<div className="w-full flex flex-row mt-4">
<SearchBar
autoFocus={true}
/>
</div>
<div className="mt-8">
<Suspense fallback={<div>...</div>}>
<RepositoryList />
</Suspense>
</div>
<Separator className="mt-5 mb-8" />
<div className="flex flex-col items-center w-fit gap-6">
<span className="font-semibold">How to search</span>
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
<HowToSection
title="Search in files or paths"
>
<QueryExample>
<Query query="test todo">test todo</Query> <QueryExplanation>(both test and todo)</QueryExplanation>
</QueryExample>
<QueryExample>
<Query query="test or todo">test <Highlight>or</Highlight> todo</Query> <QueryExplanation>(either test or todo)</QueryExplanation>
</QueryExample>
<QueryExample>
<Query query={`"exit boot"`}>{`"exit boot"`}</Query> <QueryExplanation>(exact match)</QueryExplanation>
</QueryExample>
<QueryExample>
<Query query="TODO case:yes">TODO <Highlight>case:</Highlight>yes</Query> <QueryExplanation>(case sensitive)</QueryExplanation>
</QueryExample>
</HowToSection>
<HowToSection
title="Filter results"
>
<QueryExample>
<Query query="file:README setup"><Highlight>file:</Highlight>README setup</Query> <QueryExplanation>(by filename)</QueryExplanation>
</QueryExample>
<QueryExample>
<Query query="repo:torvalds/linux test"><Highlight>repo:</Highlight>torvalds/linux test</Query> <QueryExplanation>(by repo)</QueryExplanation>
</QueryExample>
<QueryExample>
<Query query="lang:typescript"><Highlight>lang:</Highlight>typescript</Query> <QueryExplanation>(by language)</QueryExplanation>
</QueryExample>
<QueryExample>
<Query query="branch:HEAD"><Highlight>branch:</Highlight>HEAD</Query> <QueryExplanation>(by branch)</QueryExplanation>
</QueryExample>
</HowToSection>
<HowToSection
title="Advanced"
>
<QueryExample>
<Query query="file:\.py$"><Highlight>file:</Highlight>{`\\.py$`}</Query> <QueryExplanation>{`(files that end in ".py")`}</QueryExplanation>
</QueryExample>
<QueryExample>
<Query query="sym:main"><Highlight>sym:</Highlight>main</Query> <QueryExplanation>{`(symbols named "main")`}</QueryExplanation>
</QueryExample>
<QueryExample>
<Query query="todo -lang:c">todo <Highlight>-lang:c</Highlight></Query> <QueryExplanation>(negate filter)</QueryExplanation>
</QueryExample>
<QueryExample>
<Query query="content:README"><Highlight>content:</Highlight>README</Query> <QueryExplanation>(search content only)</QueryExplanation>
</QueryExample>
</HowToSection>
</div>
</div>
</div>
</div>
)
}

const RepositoryList = async () => {
const _repos = await listRepositories();

if (isServiceError(_repos)) {
return null;
}

const repos = _repos.List.Repos.map((repo) => repo.Repository);

if (repos.length === 0) {
return <span>
Get started
<a
href="https://github.com/TaqlaAI/sourcebot/blob/main/README.md"
className="text-blue-500"
>
{` configuring Sourcebot.`}
</a>
</span>;
}

return (
<div className="flex flex-col items-center gap-3">
<span className="text-sm">
{`Search ${repos.length} `}
<a
href="/repos"
className="text-blue-500"
>
{repos.length > 1 ? 'repositories' : 'repository'}
</a>
</span>
<RepositoryCarousel repos={repos} />
</div>
)
}

const HowToSection = ({ title, children }: { title: string, children: React.ReactNode }) => {
return (
<div className="flex flex-col gap-1">
<span className="dark:text-gray-300 text-sm mb-2 underline">{title}</span>
{children}
</div>
)

}

const Highlight = ({ children }: { children: React.ReactNode }) => {
return (
<span className="text-blue-700 dark:text-blue-500">
{children}
</span>
)
}

const QueryExample = ({ children }: { children: React.ReactNode }) => {
return (
<span className="text-sm font-mono">
{children}
</span>
)
}

const QueryExplanation = ({ children }: { children: React.ReactNode }) => {
return (
<span className="text-gray-500 dark:text-gray-400 ml-3">
{children}
</span>
)
}

const Query = ({ query, children }: { query: string, children: React.ReactNode }) => {
return (
<a
href={`/search?query=${query}`}
className="cursor-pointer hover:underline"
>
{children}
</a>
)
}
21 changes: 19 additions & 2 deletions src/app/repos/columns.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
'use client';

import { Button } from "@/components/ui/button";
import { getRepoCodeHostInfo } from "@/lib/utils";
import { Column, ColumnDef } from "@tanstack/react-table"
import { ArrowUpDown } from "lucide-react"



export type RepositoryColumnInfo = {
name: string;
branches: {
Expand All @@ -25,6 +24,24 @@ export const columns: ColumnDef<RepositoryColumnInfo>[] = [
{
accessorKey: "name",
header: "Name",
cell: ({ row }) => {
const repo = row.original;
const info = getRepoCodeHostInfo(repo.name);
return (
<div className="flex flex-row items-center gap-2">
<span
className={info?.repoLink ? "cursor-pointer text-blue-500 hover:underline": ""}
onClick={() => {
if (info?.repoLink) {
window.open(info.repoLink, "_blank");
}
}}
>
{repo.name}
</span>
</div>
);
}
},
{
accessorKey: "branches",
Expand Down
98 changes: 98 additions & 0 deletions src/app/repositoryCarousel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
'use client';

import { Repository } from "@/lib/schemas";
import {
Carousel,
CarouselContent,
CarouselItem,
} from "@/components/ui/carousel";
import Autoscroll from "embla-carousel-auto-scroll";
import { getRepoCodeHostInfo } from "@/lib/utils";
import Image from "next/image";
import { FileIcon } from "@radix-ui/react-icons";
import clsx from "clsx";

interface RepositoryCarouselProps {
repos: Repository[];
}

export const RepositoryCarousel = ({
repos,
}: RepositoryCarouselProps) => {
return (
<Carousel
opts={{
align: "start",
loop: true,
}}
className="w-full max-w-lg"
plugins={[
Autoscroll({
startDelay: 0,
speed: 1,
stopOnMouseEnter: true,
stopOnInteraction: false,
}),
]}
>
<CarouselContent>
{repos.map((repo, index) => (
<CarouselItem key={index} className="basis-auto">
<RepositoryBadge
key={index}
repo={repo}
/>
</CarouselItem>
))}
</CarouselContent>
</Carousel>
)
};

interface RepositoryBadgeProps {
repo: Repository;
}

const RepositoryBadge = ({
repo
}: RepositoryBadgeProps) => {
const { repoIcon, repoName, repoLink } = (() => {
const info = getRepoCodeHostInfo(repo.Name);

if (info) {
return {
repoIcon: <Image
src={info.icon}
alt={info.costHostName}
className="w-4 h-4 dark:invert"
/>,
repoName: info.repoName,
repoLink: info.repoLink,
}
}

return {
repoIcon: <FileIcon className="w-4 h-4" />,
repoName: repo.Name,
repoLink: undefined,
}
})();

return (
<div
onClick={() => {
if (repoLink !== undefined) {
window.open(repoLink, "_blank");
}
}}
className={clsx("flex flex-row items-center gap-2 border rounded-md p-2 text-clip", {
"cursor-pointer": repoLink !== undefined,
})}
>
{repoIcon}
<span className="text-sm font-mono">
{repoName}
</span>
</div>
)
}
Loading

0 comments on commit 1c756d2

Please sign in to comment.