2024-11-24 21:58:05 +00:00
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
import { useQuery } from "@tanstack/react-query";
|
|
|
|
|
import { Suggestion, SuggestionMode } from "./searchSuggestionsBox";
|
|
|
|
|
import { getRepos, search } from "@/app/api/(client)/client";
|
|
|
|
|
import { useMemo } from "react";
|
2024-11-28 21:26:27 +00:00
|
|
|
import { Symbol } from "@/lib/types";
|
2024-12-17 19:00:58 +00:00
|
|
|
import { languageMetadataMap } from "@/lib/languageMetadata";
|
2024-11-28 21:26:27 +00:00
|
|
|
import {
|
|
|
|
|
VscSymbolClass,
|
|
|
|
|
VscSymbolConstant,
|
|
|
|
|
VscSymbolEnum,
|
|
|
|
|
VscSymbolField,
|
|
|
|
|
VscSymbolInterface,
|
|
|
|
|
VscSymbolMethod,
|
|
|
|
|
VscSymbolProperty,
|
|
|
|
|
VscSymbolStructure,
|
|
|
|
|
VscSymbolVariable
|
|
|
|
|
} from "react-icons/vsc";
|
2024-11-29 18:42:08 +00:00
|
|
|
import { useSearchHistory } from "@/hooks/useSearchHistory";
|
2024-11-28 21:26:27 +00:00
|
|
|
|
2024-11-24 21:58:05 +00:00
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
suggestionMode: SuggestionMode;
|
|
|
|
|
suggestionQuery: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Fetches suggestions for the search bar.
|
|
|
|
|
*/
|
|
|
|
|
export const useSuggestionsData = ({
|
|
|
|
|
suggestionMode,
|
|
|
|
|
suggestionQuery,
|
|
|
|
|
}: Props) => {
|
2024-11-28 21:26:27 +00:00
|
|
|
const { data: repoSuggestions, isLoading: _isLoadingRepos } = useQuery({
|
2024-11-24 21:58:05 +00:00
|
|
|
queryKey: ["repoSuggestions"],
|
|
|
|
|
queryFn: getRepos,
|
|
|
|
|
select: (data): Suggestion[] => {
|
|
|
|
|
return data.List.Repos
|
|
|
|
|
.map(r => r.Repository)
|
|
|
|
|
.map(r => ({
|
|
|
|
|
value: r.Name
|
|
|
|
|
}));
|
|
|
|
|
},
|
|
|
|
|
enabled: suggestionMode === "repo",
|
|
|
|
|
});
|
2024-11-28 21:26:27 +00:00
|
|
|
const isLoadingRepos = useMemo(() => suggestionMode === "repo" && _isLoadingRepos, [_isLoadingRepos, suggestionMode]);
|
2024-11-24 21:58:05 +00:00
|
|
|
|
2024-11-28 21:26:27 +00:00
|
|
|
const { data: fileSuggestions, isLoading: _isLoadingFiles } = useQuery({
|
2024-11-24 21:58:05 +00:00
|
|
|
queryKey: ["fileSuggestions", suggestionQuery],
|
|
|
|
|
queryFn: () => search({
|
|
|
|
|
query: `file:${suggestionQuery}`,
|
|
|
|
|
maxMatchDisplayCount: 15,
|
|
|
|
|
}),
|
|
|
|
|
select: (data): Suggestion[] => {
|
|
|
|
|
return data.Result.Files?.map((file) => ({
|
|
|
|
|
value: file.FileName
|
|
|
|
|
})) ?? [];
|
|
|
|
|
},
|
|
|
|
|
enabled: suggestionMode === "file"
|
|
|
|
|
});
|
2024-11-28 21:26:27 +00:00
|
|
|
const isLoadingFiles = useMemo(() => suggestionMode === "file" && _isLoadingFiles, [_isLoadingFiles, suggestionMode]);
|
|
|
|
|
|
|
|
|
|
const { data: symbolSuggestions, isLoading: _isLoadingSymbols } = useQuery({
|
|
|
|
|
queryKey: ["symbolSuggestions", suggestionQuery],
|
|
|
|
|
queryFn: () => search({
|
|
|
|
|
query: `sym:${suggestionQuery.length > 0 ? suggestionQuery : ".*"}`,
|
|
|
|
|
maxMatchDisplayCount: 15,
|
|
|
|
|
}),
|
|
|
|
|
select: (data): Suggestion[] => {
|
|
|
|
|
const symbols = data.Result.Files?.flatMap((file) => file.ChunkMatches).flatMap((chunk) => chunk.SymbolInfo ?? []);
|
|
|
|
|
if (!symbols) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// De-duplicate on symbol name & kind.
|
|
|
|
|
const symbolMap = new Map<string, Symbol>(symbols.map((symbol: Symbol) => [`${symbol.Kind}.${symbol.Sym}`, symbol]));
|
|
|
|
|
const suggestions = Array.from(symbolMap.values()).map((symbol) => ({
|
|
|
|
|
value: symbol.Sym,
|
|
|
|
|
Icon: getSymbolIcon(symbol),
|
|
|
|
|
} satisfies Suggestion));
|
|
|
|
|
|
|
|
|
|
return suggestions;
|
|
|
|
|
},
|
|
|
|
|
enabled: suggestionMode === "symbol",
|
|
|
|
|
});
|
|
|
|
|
const isLoadingSymbols = useMemo(() => suggestionMode === "symbol" && _isLoadingSymbols, [suggestionMode, _isLoadingSymbols]);
|
2024-11-24 21:58:05 +00:00
|
|
|
|
|
|
|
|
const languageSuggestions = useMemo((): Suggestion[] => {
|
2024-12-17 19:00:58 +00:00
|
|
|
return Object.keys(languageMetadataMap).map((lang) => {
|
2024-11-24 21:58:05 +00:00
|
|
|
const spotlight = [
|
|
|
|
|
"Python",
|
|
|
|
|
"Java",
|
|
|
|
|
"TypeScript",
|
|
|
|
|
"Go",
|
|
|
|
|
"C++",
|
|
|
|
|
"C#"
|
|
|
|
|
].includes(lang);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
value: lang,
|
|
|
|
|
spotlight,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}, []);
|
|
|
|
|
|
2024-11-29 18:42:08 +00:00
|
|
|
const { searchHistory } = useSearchHistory();
|
|
|
|
|
const searchHistorySuggestions = useMemo(() => {
|
|
|
|
|
return searchHistory.map(search => ({
|
|
|
|
|
value: search.query,
|
|
|
|
|
description: getDisplayTime(new Date(search.date)),
|
|
|
|
|
} satisfies Suggestion));
|
|
|
|
|
}, [searchHistory]);
|
|
|
|
|
|
2024-11-28 21:26:27 +00:00
|
|
|
const isLoadingSuggestions = useMemo(() => {
|
|
|
|
|
return isLoadingSymbols || isLoadingFiles || isLoadingRepos;
|
|
|
|
|
}, [isLoadingFiles, isLoadingRepos, isLoadingSymbols]);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
repoSuggestions: repoSuggestions ?? [],
|
|
|
|
|
fileSuggestions: fileSuggestions ?? [],
|
|
|
|
|
symbolSuggestions: symbolSuggestions ?? [],
|
|
|
|
|
languageSuggestions,
|
2024-11-29 18:42:08 +00:00
|
|
|
searchHistorySuggestions,
|
2024-11-28 21:26:27 +00:00
|
|
|
isLoadingSuggestions,
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-11-24 21:58:05 +00:00
|
|
|
|
2024-11-28 21:26:27 +00:00
|
|
|
const getSymbolIcon = (symbol: Symbol) => {
|
|
|
|
|
switch (symbol.Kind) {
|
|
|
|
|
case "methodSpec":
|
|
|
|
|
case "method":
|
|
|
|
|
case "function":
|
|
|
|
|
case "func":
|
|
|
|
|
return VscSymbolMethod;
|
|
|
|
|
case "variable":
|
|
|
|
|
return VscSymbolVariable;
|
|
|
|
|
case "class":
|
|
|
|
|
return VscSymbolClass;
|
|
|
|
|
case "const":
|
|
|
|
|
case "macro":
|
|
|
|
|
case "constant":
|
|
|
|
|
return VscSymbolConstant;
|
|
|
|
|
case "property":
|
|
|
|
|
return VscSymbolProperty;
|
|
|
|
|
case "struct":
|
|
|
|
|
return VscSymbolStructure;
|
|
|
|
|
case "field":
|
|
|
|
|
case "member":
|
|
|
|
|
return VscSymbolField;
|
|
|
|
|
case "interface":
|
|
|
|
|
return VscSymbolInterface;
|
|
|
|
|
case "enum":
|
|
|
|
|
case "enumerator":
|
|
|
|
|
return VscSymbolEnum;
|
|
|
|
|
}
|
2024-11-29 18:42:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getDisplayTime = (createdAt: Date) => {
|
|
|
|
|
const now = new Date();
|
|
|
|
|
const minutes = (now.getTime() - createdAt.getTime()) / (1000 * 60);
|
|
|
|
|
const hours = minutes / 60;
|
|
|
|
|
const days = hours / 24;
|
|
|
|
|
const months = days / 30;
|
|
|
|
|
|
|
|
|
|
const formatTime = (value: number, unit: 'minute' | 'hour' | 'day' | 'month') => {
|
|
|
|
|
const roundedValue = Math.floor(value);
|
|
|
|
|
if (roundedValue < 2) {
|
|
|
|
|
return `${roundedValue} ${unit} ago`;
|
|
|
|
|
} else {
|
|
|
|
|
return `${roundedValue} ${unit}s ago`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (minutes < 1) {
|
|
|
|
|
return 'just now';
|
|
|
|
|
} else if (minutes < 60) {
|
|
|
|
|
return formatTime(minutes, 'minute');
|
|
|
|
|
} else if (hours < 24) {
|
|
|
|
|
return formatTime(hours, 'hour');
|
|
|
|
|
} else if (days < 30) {
|
|
|
|
|
return formatTime(days, 'day');
|
|
|
|
|
} else {
|
|
|
|
|
return formatTime(months, 'month');
|
|
|
|
|
}
|
2024-12-17 19:00:58 +00:00
|
|
|
}
|