From 45c176f7ae42f8a2300d3a8160f2e7dfb9e85e5d Mon Sep 17 00:00:00 2001 From: bkellam Date: Mon, 2 Sep 2024 18:46:43 -0700 Subject: [PATCH] Several things: (1) Add a basic landing page and move search into /search route. (2) Make the search bar a form instead of being fancy and using debounce. (3) Add react-query for better query handling. --- package.json | 8 +- public/next.svg | 1 - public/sb_logo_dark_large.png | Bin 0 -> 26903 bytes public/sb_logo_large_3.png | Bin 4944 -> 0 bytes public/sb_logo_light_large.png | Bin 0 -> 24322 bytes public/vercel.svg | 1 - src/app/api/search/route.ts | 4 +- src/app/layout.tsx | 17 ++- src/app/page.tsx | 163 +++++----------------- src/app/queryClientProvider.tsx | 14 ++ src/app/{ => search}/codePreview.tsx | 6 +- src/app/search/page.tsx | 183 +++++++++++++++++++++++++ src/app/{ => search}/searchResults.tsx | 0 src/app/searchBar.tsx | 118 +++++++++------- src/app/settingsDropdown.tsx | 14 +- src/components/ui/form.tsx | 178 ++++++++++++++++++++++++ src/components/ui/label.tsx | 26 ++++ src/hooks/useKeymapType.ts | 9 ++ yarn.lock | 56 ++++++-- 19 files changed, 592 insertions(+), 206 deletions(-) delete mode 100644 public/next.svg create mode 100644 public/sb_logo_dark_large.png delete mode 100644 public/sb_logo_large_3.png create mode 100644 public/sb_logo_light_large.png delete mode 100644 public/vercel.svg create mode 100644 src/app/queryClientProvider.tsx rename src/app/{ => search}/codePreview.tsx (97%) create mode 100644 src/app/search/page.tsx rename src/app/{ => search}/searchResults.tsx (100%) create mode 100644 src/components/ui/form.tsx create mode 100644 src/components/ui/label.tsx create mode 100644 src/hooks/useKeymapType.ts diff --git a/package.json b/package.json index c2cb612d..b1ba8900 100644 --- a/package.json +++ b/package.json @@ -14,13 +14,15 @@ "@codemirror/search": "^6.5.6", "@codemirror/state": "^6.4.1", "@codemirror/view": "^6.33.0", + "@hookform/resolvers": "^3.9.0", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", "@replit/codemirror-vim": "^6.2.1", - "@uidotdev/usehooks": "^2.4.1", + "@tanstack/react-query": "^5.53.3", "@uiw/react-codemirror": "^4.23.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", @@ -30,11 +32,13 @@ "next-themes": "^0.3.0", "react": "^18", "react-dom": "^18", + "react-hook-form": "^7.53.0", "react-resizable-panels": "^2.1.1", "sharp": "^0.33.5", "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", - "use-debounce": "^10.0.3" + "usehooks-ts": "^3.1.0", + "zod": "^3.23.8" }, "devDependencies": { "@types/node": "^20", diff --git a/public/next.svg b/public/next.svg deleted file mode 100644 index 5174b28c..00000000 --- a/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/sb_logo_dark_large.png b/public/sb_logo_dark_large.png new file mode 100644 index 0000000000000000000000000000000000000000..6abddb770e89f9a41874c659756766852468f46b GIT binary patch literal 26903 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

|sl7=P++9>-H1f7fn~ef7D};9_X`2}q5rfR+ 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 B5t9G_ literal 0 HcmV?d00001 diff --git a/public/sb_logo_large_3.png b/public/sb_logo_large_3.png deleted file mode 100644 index 7265839ec41f75b4a53d51c45d24333b60df0dd1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4944 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 diff --git a/public/sb_logo_light_large.png b/public/sb_logo_light_large.png new file mode 100644 index 0000000000000000000000000000000000000000..37d136fa7d6ff2dbb80c1a4429e69f18f17896b8 GIT binary patch literal 24322 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 \ No newline at end of file diff --git a/src/app/api/search/route.ts b/src/app/api/search/route.ts index e9b74872..dfa2e7fc 100644 --- a/src/app/api/search/route.ts +++ b/src/app/api/search/route.ts @@ -16,6 +16,6 @@ export async function GET(request: NextRequest) { ); const res = await fetch(url); const data = await res.json(); - - return Response.json({ data }) + + return Response.json({ ...data }) } \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 3f74f658..29214f13 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,6 +3,7 @@ import { Inter } from "next/font/google"; import "./globals.css"; import { ThemeProvider } from "next-themes"; import { Suspense } from "react"; +import { QueryClientProvider } from "./queryClientProvider"; const inter = Inter({ subsets: ["latin"] }); @@ -29,13 +30,15 @@ export default function RootLayout({ enableSystem disableTransitionOnChange > - {/* - @todo : ideally we don't wrap everything in a suspense boundary. - @see : https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout - */} - - {children} - + + {/* + @todo : ideally we don't wrap everything in a suspense boundary. + @see : https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout + */} + + {children} + + diff --git a/src/app/page.tsx b/src/app/page.tsx index d28dd5e9..db96f926 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,140 +1,51 @@ 'use client'; -import { - ResizableHandle, - ResizablePanel, - ResizablePanelGroup, -} from "@/components/ui/resizable"; -import { Separator } from "@/components/ui/separator"; -import { useNonEmptyQueryParam } from "@/hooks/useNonEmptyQueryParam"; -import { GetSourceResponse, KeymapType, pathQueryParamName, repoQueryParamName, ZoektFileMatch } from "@/lib/types"; -import { createPathWithQueryParams } from "@/lib/utils"; -import { SymbolIcon } from "@radix-ui/react-icons"; import Image from "next/image"; -import { useRouter } from "next/navigation"; -import { useState } from "react"; -import logoDark from "../../public/sb_logo_dark.png"; -import logoLight from "../../public/sb_logo_light.png"; -import { CodePreview, CodePreviewFile } from "./codePreview"; +import logoDark from "../../public/sb_logo_dark_large.png"; +import logoLight from "../../public/sb_logo_light_large.png"; import { SearchBar } from "./searchBar"; -import { SearchResults } from "./searchResults"; import { SettingsDropdown } from "./settingsDropdown"; -import { useLocalStorage } from "@uidotdev/usehooks"; +import { GitHubLogoIcon } from "@radix-ui/react-icons"; +import { Button } from "@/components/ui/button"; + +const SOURCEBOT_GITHUB_URL = "https://github.com/TaqlaAI/sourcebot-search"; export default function Home() { - const router = useRouter(); - const defaultQuery = useNonEmptyQueryParam("query") ?? ""; - const defaultNumResults = useNonEmptyQueryParam("numResults"); - - const [query, setQuery] = useState(defaultQuery); - const [numResults, _setNumResults] = useState(defaultNumResults && !isNaN(Number(defaultNumResults)) ? Number(defaultNumResults) : 100); - - const [isCodePanelOpen, setIsCodePanelOpen] = useState(false); - const [previewFile, setPreviewFile] = useState(undefined); - const [selectedMatchIndex, setSelectedMatchIndex] = useState(0); - - const [fileMatches, setFileMatches] = useState([]); - const [isLoading, setIsLoading] = useState(false); - const [searchDurationMs, setSearchDurationMs] = useState(0); - - const [keymapType, saveKeymapType] = useLocalStorage("keymapType", "default"); - - // @todo: We need to be able to handle the case when the user navigates backwards / forwards. - // Currently we do not re-query. return ( -

+
{/* TopBar */} -
-
-
- {"Sourcebot - {"Sourcebot - setQuery(query)} - onLoadingChange={(isLoading) => setIsLoading(isLoading)} - onSearchResult={(result) => { - if (result) { - setFileMatches(result.FileMatches ?? []); - setSearchDurationMs(Math.round(result.Stats.Duration / 1000000)); - } - - router.push(`?query=${query}&numResults=${numResults}`); - }} - /> - {isLoading && ( - - )} -
- -
- -
-

Results for: {fileMatches.length} files in {searchDurationMs} ms

-
- -
- - {/* Search Results & Code Preview */} - - - { - const url = createPathWithQueryParams( - `http://localhost:3000/api/source`, - [pathQueryParamName, match.FileName], - [repoQueryParamName, match.Repo] - ); - - // @todo : this query should definitely be cached s.t., when the user is switching between files, - // we aren't re-fetching the same file. - fetch(url) - .then(response => response.json()) - .then((body: GetSourceResponse) => { - if (body.encoding !== "base64") { - throw new Error("Expected base64 encoding"); - } - - const content = atob(body.content); - setSelectedMatchIndex(0); - setPreviewFile({ - content: content, - filepath: match.FileName, - matches: match.Matches, - }); - setIsCodePanelOpen(true); - }); +
+
+ + +
+
+
+
+ {"Sourcebot - - - - -
- ); + +
+ +
+ + + ) } diff --git a/src/app/queryClientProvider.tsx b/src/app/queryClientProvider.tsx new file mode 100644 index 00000000..41e7ff0d --- /dev/null +++ b/src/app/queryClientProvider.tsx @@ -0,0 +1,14 @@ +'use client'; + +import * as React from "react" +import { QueryClient, QueryClientProvider as QueryClientProviderBase, QueryClientProviderProps } from "@tanstack/react-query" + +const queryClient = new QueryClient(); + +export const QueryClientProvider = ({ children, ...props }: Omit) => { + return ( + + {children} + + ) +} \ No newline at end of file diff --git a/src/app/codePreview.tsx b/src/app/search/codePreview.tsx similarity index 97% rename from src/app/codePreview.tsx rename to src/app/search/codePreview.tsx index 28e11cc4..df9e88c5 100644 --- a/src/app/codePreview.tsx +++ b/src/app/search/codePreview.tsx @@ -3,6 +3,7 @@ import { Button } from "@/components/ui/button"; import { ScrollArea } from "@/components/ui/scroll-area"; import { useExtensionWithDependency } from "@/hooks/useExtensionWithDependency"; +import { useKeymapType } from "@/hooks/useKeymapType"; import { gutterWidthExtension } from "@/lib/extensions/gutterWidthExtension"; import { markMatches, searchResultHighlightExtension } from "@/lib/extensions/searchResultHighlightExtension"; import { ZoektMatch } from "@/lib/types"; @@ -26,7 +27,6 @@ export interface CodePreviewFile { interface CodePreviewProps { file?: CodePreviewFile; - keymapType: "default" | "vim"; selectedMatchIndex: number; onSelectedMatchIndexChange: (index: number) => void; onClose: () => void; @@ -34,7 +34,6 @@ interface CodePreviewProps { export const CodePreview = ({ file, - keymapType, selectedMatchIndex, onSelectedMatchIndexChange, onClose, @@ -42,6 +41,7 @@ export const CodePreview = ({ const editorRef = useRef(null); const { theme: _theme, systemTheme } = useTheme(); + const [ keymapType ] = useKeymapType(); const theme = useMemo(() => { if (_theme === "system") { @@ -90,7 +90,7 @@ export const CodePreview = ({ } markMatches(selectedMatchIndex, file.matches, editorRef.current.view); - }, [file, selectedMatchIndex]); + }, [file, file?.matches, selectedMatchIndex]); const onUpClicked = useCallback(() => { onSelectedMatchIndexChange(selectedMatchIndex - 1); diff --git a/src/app/search/page.tsx b/src/app/search/page.tsx new file mode 100644 index 00000000..8c71c9ae --- /dev/null +++ b/src/app/search/page.tsx @@ -0,0 +1,183 @@ +'use client'; + +import { + ResizableHandle, + ResizablePanel, + ResizablePanelGroup, +} from "@/components/ui/resizable"; +import { Separator } from "@/components/ui/separator"; +import { useNonEmptyQueryParam } from "@/hooks/useNonEmptyQueryParam"; +import { GetSourceResponse, pathQueryParamName, repoQueryParamName, ZoektFileMatch, ZoektSearchResponse } from "@/lib/types"; +import { createPathWithQueryParams } from "@/lib/utils"; +import { SymbolIcon } from "@radix-ui/react-icons"; +import { useQuery } from "@tanstack/react-query"; +import Image from "next/image"; +import { useMemo, useState } from "react"; +import logoDark from "../../../public/sb_logo_dark.png"; +import logoLight from "../../../public/sb_logo_light.png"; +import { SearchBar } from "../searchBar"; +import { SettingsDropdown } from "../settingsDropdown"; +import { CodePreview, CodePreviewFile } from "./codePreview"; +import { SearchResults } from "./searchResults"; +import { useRouter } from "next/navigation"; + +export default function SearchPage() { + const router = useRouter(); + const searchQuery = useNonEmptyQueryParam("query") ?? ""; + const numResults = useNonEmptyQueryParam("numResults") ?? "100"; + + const [selectedMatchIndex, setSelectedMatchIndex] = useState(0); + const [selectedFile, setSelectedFile] = useState(undefined); + + const { data: searchResponse, isLoading } = useQuery({ + queryKey: ["search", searchQuery, numResults], + queryFn: async (): Promise => { + console.log("Fetching search results"); + const result = await fetch(`/api/search?query=${searchQuery}&numResults=${numResults}`) + .then(response => response.json()); + console.log("Done"); + return result; + }, + enabled: searchQuery.length > 0, + }); + + const { fileMatches, searchDurationMs } = useMemo((): { fileMatches: ZoektFileMatch[], searchDurationMs: number } => { + if (!searchResponse) { + return { + fileMatches: [], + searchDurationMs: 0, + }; + } + return { + fileMatches: searchResponse.result.FileMatches ?? [], + searchDurationMs: Math.round(searchResponse.result.Stats.Duration / 1000000), + } + }, [searchResponse]); + + + return ( +
+ {/* TopBar */} +
+
+
+
{ + router.push("/"); + }} + > + {"Sourcebot + {"Sourcebot +
+ + {isLoading && ( + + )} +
+ +
+ +
+

Results for: {fileMatches.length} files in {searchDurationMs} ms

+
+ +
+ + {/* Search Results & Code Preview */} + + + { + setSelectedFile(match); + setSelectedMatchIndex(0); + }} + /> + + + + +
+ ); +} + +interface CodePreviewWrapperProps { + fileMatch?: ZoektFileMatch; + onClose: () => void; + selectedMatchIndex: number; + onSelectedMatchIndexChange: (index: number) => void; +} + +const CodePreviewWrapper = ({ + fileMatch, + onClose, + selectedMatchIndex, + onSelectedMatchIndexChange, +}: CodePreviewWrapperProps) => { + + const { data: file } = useQuery({ + queryKey: ["source", fileMatch?.FileName, fileMatch?.Repo], + queryFn: async (): Promise => { + if (!fileMatch) { + return undefined; + } + + const url = createPathWithQueryParams( + `/api/source`, + [pathQueryParamName, fileMatch.FileName], + [repoQueryParamName, fileMatch.Repo] + ); + + const result = await fetch(url) + .then(response => response.json()) + .then((body: GetSourceResponse) => { + if (body.encoding !== "base64") { + throw new Error("Expected base64 encoding"); + } + + const content = atob(body.content); + return { + content, + filepath: fileMatch.FileName, + matches: fileMatch.Matches, + }; + }); + return result; + }, + enabled: fileMatch !== undefined, + }); + + return ( + + ) + +} \ No newline at end of file diff --git a/src/app/searchResults.tsx b/src/app/search/searchResults.tsx similarity index 100% rename from src/app/searchResults.tsx rename to src/app/search/searchResults.tsx diff --git a/src/app/searchBar.tsx b/src/app/searchBar.tsx index 7a3c8c6d..327e8972 100644 --- a/src/app/searchBar.tsx +++ b/src/app/searchBar.tsx @@ -1,63 +1,85 @@ 'use client'; +import { + Form, + FormControl, + FormField, + FormItem, + FormMessage, +} from "@/components/ui/form"; import { Input } from "@/components/ui/input"; -import { ZoektResult, ZoektSearchResponse } from "@/lib/types"; -import { useEffect } from "react"; -import { useDebouncedCallback } from "use-debounce"; +import { cn } from "@/lib/utils"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { cva } from "class-variance-authority"; +import { useRouter } from "next/navigation"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; interface SearchBarProps { - query: string; - numResults: number; - onLoadingChange: (isLoading: boolean) => void; - onQueryChange: (query: string) => void; - onSearchResult: (result?: ZoektResult) => void, + className?: string; + size?: "default" | "sm"; + defaultQuery?: string; } -export const SearchBar = ({ - query, - numResults, - onLoadingChange, - onQueryChange, - onSearchResult, -}: SearchBarProps) => { - const SEARCH_DEBOUNCE_MS = 200; +const formSchema = z.object({ + query: z.string(), +}); - // @todo : we should probably be cancelling any running requests - const search = useDebouncedCallback((query: string) => { - if (query === "") { - onSearchResult(undefined); - return; +const searchBarVariants = cva( + "w-full", + { + variants: { + size: { + default: "h-10", + sm: "h-8" + } + }, + defaultVariants: { + size: "default", } - console.log('making query...'); + } +) - onLoadingChange(true); - fetch(`http://localhost:3000/api/search?query=${query}&numResults=${numResults}`) - .then(response => response.json()) - .then(({ data }: { data: ZoektSearchResponse }) => { - onSearchResult(data.result); - }) - // @todo : error handling - .catch(error => { - console.error('Error:', error); - }).finally(() => { - console.log('done making query'); - onLoadingChange(false); - }); - }, SEARCH_DEBOUNCE_MS); +export const SearchBar = ({ + className, + size, + defaultQuery, +}: SearchBarProps) => { + const router = useRouter(); + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + query: defaultQuery ?? "", + } + }); - useEffect(() => { - search(query); - }, [query, search]); + const onSubmit = (values: z.infer) => { + router.push(`/search?query=${values.query}&numResults=100`); + } return ( - { - const query = e.target.value; - onQueryChange(query); - }} - /> +
+ + ( + + + + + + + )} + /> + + ) } \ No newline at end of file diff --git a/src/app/settingsDropdown.tsx b/src/app/settingsDropdown.tsx index 74d037e5..2fc75879 100644 --- a/src/app/settingsDropdown.tsx +++ b/src/app/settingsDropdown.tsx @@ -23,18 +23,20 @@ import { import { useTheme } from "next-themes" import { useMemo } from "react" import { KeymapType } from "@/lib/types" +import { cn } from "@/lib/utils" +import { useKeymapType } from "@/hooks/useKeymapType" interface SettingsDropdownProps { - keymapType: KeymapType; - onKeymapTypeChange: (keymapType: KeymapType) => void; + menuButtonClassName?: string; } export const SettingsDropdown = ({ - keymapType, - onKeymapTypeChange, + menuButtonClassName, }: SettingsDropdownProps) => { const { theme: _theme, setTheme } = useTheme(); + const [ keymapType, setKeymapType ] = useKeymapType(); + const theme = useMemo(() => { return _theme ?? "light"; }, [_theme]); @@ -55,7 +57,7 @@ export const SettingsDropdown = ({ return ( - @@ -91,7 +93,7 @@ export const SettingsDropdown = ({ - onKeymapTypeChange(value as KeymapType)}> + setKeymapType(value as KeymapType)}> Default diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx new file mode 100644 index 00000000..ce264aef --- /dev/null +++ b/src/components/ui/form.tsx @@ -0,0 +1,178 @@ +"use client" + +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { Slot } from "@radix-ui/react-slot" +import { + Controller, + ControllerProps, + FieldPath, + FieldValues, + FormProvider, + useFormContext, +} from "react-hook-form" + +import { cn } from "@/lib/utils" +import { Label } from "@/components/ui/label" + +const Form = FormProvider + +type FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +> = { + name: TName +} + +const FormFieldContext = React.createContext( + {} as FormFieldContextValue +) + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +>({ + ...props +}: ControllerProps) => { + return ( + + + + ) +} + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext) + const itemContext = React.useContext(FormItemContext) + const { getFieldState, formState } = useFormContext() + + const fieldState = getFieldState(fieldContext.name, formState) + + if (!fieldContext) { + throw new Error("useFormField should be used within ") + } + + const { id } = itemContext + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + } +} + +type FormItemContextValue = { + id: string +} + +const FormItemContext = React.createContext( + {} as FormItemContextValue +) + +const FormItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const id = React.useId() + + return ( + +
+ + ) +}) +FormItem.displayName = "FormItem" + +const FormLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + const { error, formItemId } = useFormField() + + return ( +