"use client"; import React, { useEffect, useState } from "react"; import { Command } from "cmdk"; import { observer } from "mobx-react"; import { useRouter } from "next/navigation"; import { FileText, FolderPlus, Search, Settings } from "lucide-react"; import { Dialog, Transition } from "@headlessui/react"; // ui import { Loader } from "@plane/ui"; // components import { CommandPaletteThemeActions, CommandPaletteHelpActions } from "@/components/command-palette"; import { EmptyState } from "@/components/empty-state"; // constants import { EmptyStateType } from "@/constants/empty-state"; // hooks import { useCommandPalette } from "@/hooks/store"; import useDebounce from "@/hooks/use-debounce"; // plane web components import { PagesAppCommandPaletteSearchResults } from "@/plane-web/components/command-palette"; // plane web services import { AppService } from "@/plane-web/services/app.service"; // plane web types import { IAppSearchResults } from "@/plane-web/types"; const appService = new AppService(); type Props = { workspaceSlug: string; }; export const PagesAppCommandModal: React.FC = observer((props) => { const { workspaceSlug } = props; // states const [placeholder, setPlaceholder] = useState("Type a command or search..."); const [resultsCount, setResultsCount] = useState(0); const [isLoading, setIsLoading] = useState(false); const [isSearching, setIsSearching] = useState(false); const [searchTerm, setSearchTerm] = useState(""); const [results, setResults] = useState({ results: { workspace: [], page: [], }, }); const [pages, setPages] = useState([]); // router const router = useRouter(); // store hooks const { isCommandPaletteOpen, toggleCommandPaletteModal, toggleCreatePageModal } = useCommandPalette(); const page = pages[pages.length - 1]; const debouncedSearchTerm = useDebounce(searchTerm, 500); const closePalette = () => { toggleCommandPaletteModal(false); }; const createNewWorkspace = () => { closePalette(); router.push("/create-workspace"); }; useEffect( () => { if (!workspaceSlug) return; setIsLoading(true); if (debouncedSearchTerm) { setIsSearching(true); appService .searchApp(workspaceSlug.toString(), { search: debouncedSearchTerm, }) .then((results) => { setResults(results); const count = Object.keys(results.results).reduce( (accumulator, key) => (results.results as any)[key].length + accumulator, 0 ); setResultsCount(count); }) .finally(() => { setIsLoading(false); setIsSearching(false); }); } else { setResults({ results: { workspace: [], page: [], }, }); setIsLoading(false); setIsSearching(false); } }, [debouncedSearchTerm, workspaceSlug] // Only call effect if debounced search term changes ); return ( setSearchTerm("")} as={React.Fragment}> closePalette()}>
{ if (value.toLowerCase().includes(search.toLowerCase())) return 1; return 0; }} onKeyDown={(e) => { // when search term is not empty, esc should clear the search term if (e.key === "Escape" && searchTerm) setSearchTerm(""); // when user tries to close the modal with esc if (e.key === "Escape" && !page && !searchTerm) closePalette(); // Escape goes to previous page // Backspace goes to previous page when search is empty if (e.key === "Escape" || (e.key === "Backspace" && !searchTerm)) { e.preventDefault(); setPages((pages) => pages.slice(0, -1)); setPlaceholder("Type a command or search..."); } }} >
{searchTerm !== "" && (
Search results for{" "} {'"'} {searchTerm} {'"'} {" "} in workspace
)} {!isLoading && resultsCount === 0 && searchTerm !== "" && debouncedSearchTerm !== "" && (
)} {(isLoading || isSearching) && ( )} {debouncedSearchTerm !== "" && ( )} {!page && ( <> { closePalette(); toggleCreatePageModal({ isOpen: true }); }} className="focus:outline-none" >
Create new page
D
Create new workspace
{ setPlaceholder("Change interface theme..."); setSearchTerm(""); setPages([...pages, "change-interface-theme"]); }} className="focus:outline-none" >
Change interface theme...
{/* help options */} )} {/* theme actions */} {page === "change-interface-theme" && ( { closePalette(); setPages((pages) => pages.slice(0, -1)); }} /> )}
); });