2022-02-07 01:45:18 +00:00
|
|
|
import { useCombobox } from "downshift";
|
2022-08-27 21:01:16 +00:00
|
|
|
import React, { useContext, useState, useRef } from "react";
|
|
|
|
import { SearchResult } from "minisearch";
|
2022-01-24 05:59:36 +00:00
|
|
|
|
2022-08-27 21:01:16 +00:00
|
|
|
import { SearchContext } from "./SearchProvider";
|
2022-01-24 05:59:36 +00:00
|
|
|
import styles from "../styles/SearchBar.module.css";
|
|
|
|
|
|
|
|
type Props = {
|
2022-01-30 21:55:50 +00:00
|
|
|
counts: Record<number, [number, number, number]> | null;
|
2022-02-27 06:17:52 +00:00
|
|
|
sidebarOpen: boolean;
|
2022-06-01 03:55:36 +00:00
|
|
|
placeholder: string;
|
|
|
|
onSelectResult: (item: SearchResult | null) => void;
|
|
|
|
includeCells?: boolean;
|
|
|
|
fixed?: boolean;
|
|
|
|
inputRef?: React.MutableRefObject<HTMLInputElement | null>;
|
2022-01-24 05:59:36 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
interface Mod {
|
2022-01-25 05:31:25 +00:00
|
|
|
name: string;
|
|
|
|
id: number;
|
2022-01-24 05:59:36 +00:00
|
|
|
}
|
|
|
|
|
2022-06-01 03:55:36 +00:00
|
|
|
const SearchBar: React.FC<Props> = ({
|
|
|
|
counts,
|
|
|
|
sidebarOpen,
|
|
|
|
placeholder,
|
|
|
|
onSelectResult,
|
|
|
|
includeCells = false,
|
|
|
|
fixed = false,
|
|
|
|
inputRef,
|
|
|
|
}) => {
|
2022-08-27 21:01:16 +00:00
|
|
|
const { cellSearch, modSearch, loading, loadError } =
|
|
|
|
useContext(SearchContext);
|
2022-01-24 05:59:36 +00:00
|
|
|
const searchInput = useRef<HTMLInputElement | null>(null);
|
|
|
|
const [searchFocused, setSearchFocused] = useState<boolean>(false);
|
|
|
|
const [results, setResults] = useState<SearchResult[]>([]);
|
|
|
|
|
2022-02-07 01:45:18 +00:00
|
|
|
const {
|
|
|
|
isOpen,
|
|
|
|
getMenuProps,
|
|
|
|
getInputProps,
|
|
|
|
getComboboxProps,
|
|
|
|
highlightedIndex,
|
|
|
|
getItemProps,
|
|
|
|
reset,
|
|
|
|
} = useCombobox({
|
|
|
|
items: results,
|
|
|
|
itemToString: (item) => (item ? item.name : ""),
|
|
|
|
onInputValueChange: ({ inputValue }) => {
|
2022-02-07 03:55:58 +00:00
|
|
|
if (inputValue) {
|
|
|
|
let results: SearchResult[] = [];
|
2022-03-23 15:16:25 +00:00
|
|
|
if (
|
2022-08-27 21:01:16 +00:00
|
|
|
modSearch &&
|
2022-03-23 15:16:25 +00:00
|
|
|
!/(^cell\s?-?\d+\s?,?\s?-?\d*$)|(^-?\d+\s?,\s?-?\d*$)/i.test(
|
|
|
|
inputValue
|
|
|
|
)
|
|
|
|
) {
|
2022-02-07 03:55:58 +00:00
|
|
|
results = results.concat(
|
2022-08-27 21:01:16 +00:00
|
|
|
modSearch.search(inputValue).sort((resultA, resultB) => {
|
2022-02-07 03:55:58 +00:00
|
|
|
if (counts) {
|
|
|
|
const countA = counts[resultA.id];
|
|
|
|
const countB = counts[resultB.id];
|
2022-02-07 15:43:51 +00:00
|
|
|
const scoreA = resultA.score;
|
|
|
|
const scoreB = resultB.score;
|
|
|
|
if (countA && countB && scoreA && scoreB) {
|
|
|
|
if (scoreA === scoreB) {
|
|
|
|
return countB[2] - countA[2];
|
|
|
|
} else {
|
|
|
|
return scoreB - scoreA;
|
|
|
|
}
|
|
|
|
}
|
2022-02-07 03:55:58 +00:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
2022-06-01 03:55:36 +00:00
|
|
|
if (includeCells) {
|
|
|
|
results = results.concat(cellSearch.search(inputValue));
|
|
|
|
}
|
2022-02-07 03:55:58 +00:00
|
|
|
setResults(results.splice(0, 30));
|
2022-01-24 05:59:36 +00:00
|
|
|
}
|
2022-02-07 01:45:18 +00:00
|
|
|
},
|
|
|
|
onSelectedItemChange: ({ selectedItem }) => {
|
|
|
|
if (selectedItem) {
|
|
|
|
setSearchFocused(false);
|
2022-06-01 03:55:36 +00:00
|
|
|
onSelectResult(selectedItem);
|
2022-02-07 01:45:18 +00:00
|
|
|
if (searchInput.current) searchInput.current.blur();
|
|
|
|
reset();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
2022-01-24 05:59:36 +00:00
|
|
|
|
|
|
|
return (
|
2022-02-07 01:45:18 +00:00
|
|
|
<>
|
|
|
|
<div
|
|
|
|
className={`${styles["search-bar"]} ${
|
|
|
|
searchFocused ? styles["search-bar-focused"] : ""
|
2022-06-01 03:55:36 +00:00
|
|
|
} ${fixed ? styles["search-bar-fixed"] : ""} ${
|
|
|
|
sidebarOpen ? styles["search-bar-sidebar-open"] : ""
|
|
|
|
}`}
|
2022-02-07 01:45:18 +00:00
|
|
|
{...getComboboxProps()}
|
|
|
|
>
|
|
|
|
<input
|
|
|
|
{...getInputProps({
|
|
|
|
type: "text",
|
2022-08-27 21:01:16 +00:00
|
|
|
placeholder:
|
|
|
|
modSearch && !loading ? placeholder : "Search (loading...)",
|
2022-02-07 01:45:18 +00:00
|
|
|
onFocus: () => setSearchFocused(true),
|
|
|
|
onBlur: () => {
|
|
|
|
if (!isOpen) setSearchFocused(false);
|
|
|
|
},
|
2022-08-27 21:01:16 +00:00
|
|
|
disabled: !modSearch,
|
2022-06-01 03:55:36 +00:00
|
|
|
ref: (ref) => {
|
|
|
|
searchInput.current = ref;
|
|
|
|
if (inputRef) inputRef.current = ref;
|
|
|
|
},
|
2022-02-07 01:45:18 +00:00
|
|
|
})}
|
|
|
|
/>
|
|
|
|
<ul
|
|
|
|
className={styles["search-results"]}
|
|
|
|
style={!isOpen ? { display: "none" } : {}}
|
|
|
|
{...getMenuProps()}
|
|
|
|
>
|
|
|
|
{isOpen &&
|
2022-02-07 03:55:58 +00:00
|
|
|
results.map((result, index) => (
|
|
|
|
<li
|
|
|
|
key={result.id}
|
|
|
|
{...getItemProps({ item: result, index })}
|
|
|
|
className={`${
|
|
|
|
highlightedIndex === index ? styles["highlighted-result"] : ""
|
|
|
|
}`}
|
|
|
|
>
|
|
|
|
{result.name}
|
|
|
|
</li>
|
|
|
|
))}
|
2022-08-27 21:01:16 +00:00
|
|
|
{loadError && (
|
2022-08-21 05:54:53 +00:00
|
|
|
<div className={styles.error}>
|
2022-08-27 21:01:16 +00:00
|
|
|
Error loading mod search index: {loadError}.
|
2022-08-21 05:54:53 +00:00
|
|
|
</div>
|
|
|
|
)}
|
2022-01-24 05:59:36 +00:00
|
|
|
</ul>
|
2022-02-07 01:45:18 +00:00
|
|
|
</div>
|
|
|
|
</>
|
2022-01-24 05:59:36 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default SearchBar;
|