diff --git a/src/components/CmdKMenu.tsx b/src/components/CmdKMenu.tsx index 9f91137f..9329985d 100644 --- a/src/components/CmdKMenu.tsx +++ b/src/components/CmdKMenu.tsx @@ -1,26 +1,13 @@ import { JLCPCBImportDialog } from "@/components/JLCPCBImportDialog" -import { - CommandDialog, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, -} from "@/components/ui/command" import { useAxios } from "@/hooks/use-axios" import { useGlobalStore } from "@/hooks/use-global-store" import { useNotImplementedToast } from "@/hooks/use-toast" +import { Command } from "cmdk" import { Snippet } from "fake-snippets-api/lib/db/schema" import React from "react" import { useQuery } from "react-query" -type SnippetType = - | "board" - | "package" - | "model" - | "footprint" - | "snippet" - | "search-result" +type SnippetType = "board" | "package" | "model" | "footprint" | "snippet" interface Template { name: string @@ -34,22 +21,7 @@ interface ImportOption { special?: boolean } -interface CommandItemData { - label: string - href?: string - type: SnippetType - disabled?: boolean - action?: () => void - subtitle?: string - description?: string -} - -interface CommandGroup { - group: string - items: CommandItemData[] -} - -const CmdKMenu: React.FC = () => { +const CmdKMenu = () => { const [open, setOpen] = React.useState(false) const [searchQuery, setSearchQuery] = React.useState("") const [isJLCPCBDialogOpen, setIsJLCPCBDialogOpen] = React.useState(false) @@ -57,48 +29,48 @@ const CmdKMenu: React.FC = () => { const axios = useAxios() const currentUser = useGlobalStore((s) => s.session?.github_username) - React.useEffect(() => { - const handleKeyDown = (e: KeyboardEvent) => { - if ((e.metaKey || e.ctrlKey) && e.key === "k") { - e.preventDefault() - setOpen((prev) => !prev) - } - } - - window.addEventListener("keydown", handleKeyDown) - return () => window.removeEventListener("keydown", handleKeyDown) - }, []) + // Search results query + const { data: searchResults = [], isLoading: isSearching } = useQuery( + ["snippetSearch", searchQuery], + async () => { + if (!searchQuery) return [] + const { data } = await axios.get("/snippets/search", { + params: { q: searchQuery }, + }) + return data.snippets || [] + }, + { + enabled: Boolean(searchQuery), + }, + ) - const { data: recentSnippets } = useQuery( + // Recent snippets query + const { data: recentSnippets = [] } = useQuery( ["userSnippets", currentUser], async () => { if (!currentUser) return [] const response = await axios.get<{ snippets: Snippet[] }>( `/snippets/list?owner_name=${currentUser}`, ) - return response.data.snippets + return response.data.snippets || [] }, { - enabled: !!currentUser, + enabled: !!currentUser && !searchQuery, }, ) - // Add search results query - const { data: searchResults, isLoading: isSearching } = useQuery( - ["snippetSearch", searchQuery], - async () => { - if (!searchQuery) return [] - const { data } = await axios.get("/snippets/search", { - params: { q: searchQuery }, - }) - return data.snippets - }, - { - enabled: Boolean(searchQuery), - }, - ) + React.useEffect(() => { + const down = (e: KeyboardEvent) => { + if ((e.metaKey || e.ctrlKey) && e.key === "k") { + e.preventDefault() + setOpen((prev) => !prev) + } + } + + document.addEventListener("keydown", down) + return () => document.removeEventListener("keydown", down) + }, []) - // Templates and import options const blankTemplates: Template[] = [ { name: "Blank Circuit Board", type: "board" }, { name: "Blank Circuit Module", type: "package" }, @@ -115,134 +87,191 @@ const CmdKMenu: React.FC = () => { { name: "JLCPCB Component", type: "package", special: true }, ] - // Combine regular commands with search results - const commands: CommandGroup[] = [ - ...(searchResults && searchResults.length > 0 - ? [ - { - group: "Search Results", - items: searchResults.map((snippet: any) => ({ - label: snippet.name, - href: `/editor?snippet_id=${snippet.snippet_id}`, - type: "search-result" as const, - subtitle: `By ${snippet.owner_name}`, - description: snippet.description, - })), - }, - ] - : []), - { - group: "Recent Snippets", - items: (recentSnippets?.slice(0, 6) || []).map((snippet) => ({ - label: snippet.unscoped_name, - href: `/editor?snippet_id=${snippet.snippet_id}`, - type: "snippet" as const, - subtitle: `Last edited: ${new Date(snippet.updated_at).toLocaleDateString()}`, - })), - }, - { - group: "Start Blank Snippet", - items: blankTemplates.map((template) => ({ - label: template.name, - href: template.disabled - ? undefined - : `/editor?template=${template.name.toLowerCase().replace(/ /g, "-")}`, - type: template.type, - disabled: template.disabled, - })), - }, - { - group: "Start from Template", - items: templates.map((template) => ({ - label: template.name, - href: `/editor?template=${template.name.toLowerCase().replace(/ /g, "-")}`, - type: template.type, - })), - }, - { - group: "Import", - items: importOptions.map((option) => ({ - label: `Import ${option.name}`, - action: option.special - ? () => { - setOpen(false) - setIsJLCPCBDialogOpen(true) - } - : () => { - setOpen(false) - toastNotImplemented(`${option.name} Import`) - }, - type: option.type, - })), - }, - ] - - const filteredCommands = commands - .map((group) => ({ - ...group, - items: group.items.filter((command) => - command.label.toLowerCase().includes(searchQuery.toLowerCase()), - ), - })) - .filter((group) => group.items.length > 0) - return ( <> - - - - {isSearching && ( - - Searching... - - )} + +
+ + + + +
+ + + {isSearching ? ( + + Loading results... + + ) : ( + <> + {searchQuery && searchResults.length > 0 && ( + + {searchResults.map((snippet: Snippet) => ( + { + window.location.href = `/editor?snippet_id=${snippet.snippet_id}` + setOpen(false) + }} + className="flex items-center justify-between px-2 py-1.5 rounded-sm text-sm hover:bg-gray-100 dark:hover:bg-gray-700 cursor-default" + > +
+ + {snippet.name || snippet.unscoped_name} + + {snippet.description && ( + + {snippet.description} + + )} +
+ snippet +
+ ))} +
+ )} - {filteredCommands.length > 0 ? ( - filteredCommands.map((group, groupIndex) => ( - - {group.items.map((command, itemIndex) => ( - 0 && ( + + {recentSnippets.slice(0, 6).map((snippet) => ( + { + window.location.href = `/editor?snippet_id=${snippet.snippet_id}` + setOpen(false) + }} + className="flex items-center justify-between px-2 py-1.5 rounded-sm text-sm hover:bg-gray-100 dark:hover:bg-gray-700 cursor-default" + > +
+ + {snippet.unscoped_name} + + + Last edited:{" "} + {new Date(snippet.updated_at).toLocaleDateString()} + +
+ snippet +
+ ))} +
+ )} + + + {blankTemplates.map((template) => ( + { - if (command.action) { - command.action() - } else if (command.href && !command.disabled) { - window.location.href = command.href + if (!template.disabled) { + window.location.href = `/editor?template=${template.name.toLowerCase().replace(/ /g, "-")}` setOpen(false) } }} - disabled={command.disabled} - className="flex items-center justify-between" + className="flex items-center justify-between px-2 py-1.5 rounded-sm text-sm hover:bg-gray-100 dark:hover:bg-gray-700 cursor-default disabled:opacity-50" > -
- {command.label} - {command.subtitle && ( - - {command.subtitle} - - )} - {command.description && ( - - {command.description} - - )} -
- - {command.type} + + {template.name} -
+ + {template.type} + + ))} -
- )) - ) : ( - No results found. + + + + {templates.map((template) => ( + { + window.location.href = `/editor?template=${template.name.toLowerCase().replace(/ /g, "-")}` + setOpen(false) + }} + className="flex items-center justify-between px-2 py-1.5 rounded-sm text-sm hover:bg-gray-100 dark:hover:bg-gray-700 cursor-default" + > + + {template.name} + + + {template.type} + + + ))} + + + + {importOptions.map((option) => ( + { + if (option.special) { + setOpen(false) + setIsJLCPCBDialogOpen(true) + } else { + setOpen(false) + toastNotImplemented(`${option.name} Import`) + } + }} + className="flex items-center justify-between px-2 py-1.5 rounded-sm text-sm hover:bg-gray-100 dark:hover:bg-gray-700 cursor-default" + > + + Import {option.name} + + {option.type} + + ))} + + + {searchQuery && !searchResults.length && !isSearching && ( + + No results found. + + )} + )} -
-
+ +