2022-01-24 05:59:36 +00:00
|
|
|
import React, { useEffect, useState, useRef } from "react";
|
|
|
|
import { useRouter } from "next/router";
|
|
|
|
import type mapboxgl from "mapbox-gl";
|
|
|
|
import Fuse from "fuse.js";
|
2022-01-25 05:31:25 +00:00
|
|
|
import useSWRImmutable from "swr/immutable";
|
2022-01-24 05:59:36 +00:00
|
|
|
|
|
|
|
import styles from "../styles/SearchBar.module.css";
|
2022-01-25 05:31:25 +00:00
|
|
|
import { join } from "path/posix";
|
2022-01-24 05:59:36 +00:00
|
|
|
|
|
|
|
type Props = {
|
|
|
|
clearSelectedCell: () => void;
|
|
|
|
map: React.MutableRefObject<mapboxgl.Map>;
|
|
|
|
};
|
|
|
|
|
|
|
|
interface Mod {
|
2022-01-25 05:31:25 +00:00
|
|
|
name: string;
|
|
|
|
id: number;
|
2022-01-24 05:59:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
interface SearchResult {
|
|
|
|
item: Mod;
|
|
|
|
refIndex: number;
|
|
|
|
}
|
|
|
|
|
2022-01-25 05:31:25 +00:00
|
|
|
const jsonFetcher = async (url: string): Promise<Mod | null> => {
|
|
|
|
const res = await fetch(url);
|
|
|
|
|
|
|
|
if (!res.ok) {
|
|
|
|
if (res.status === 404) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
const error = new Error("An error occurred while fetching the data.");
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
return res.json();
|
|
|
|
};
|
2022-01-24 05:59:36 +00:00
|
|
|
|
|
|
|
const SearchBar: React.FC<Props> = ({ clearSelectedCell, map }) => {
|
|
|
|
const router = useRouter();
|
2022-01-25 05:31:25 +00:00
|
|
|
|
|
|
|
const fuse = useRef<Fuse<Mod> | null>(null) as React.MutableRefObject<
|
|
|
|
Fuse<Mod>
|
|
|
|
>;
|
|
|
|
|
|
|
|
const { data, error } = useSWRImmutable(
|
|
|
|
`https://mods.modmapper.com/mod_search_index.json`,
|
|
|
|
jsonFetcher
|
|
|
|
);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (data && !fuse.current) {
|
|
|
|
fuse.current = new Fuse(data as unknown as Mod[], { keys: ["name"] });
|
|
|
|
}
|
|
|
|
}, [data]);
|
2022-01-24 05:59:36 +00:00
|
|
|
|
|
|
|
const searchInput = useRef<HTMLInputElement | null>(null);
|
|
|
|
const [search, setSearch] = useState<string>("");
|
|
|
|
const [searchFocused, setSearchFocused] = useState<boolean>(false);
|
|
|
|
const [clickingResult, setClickingResult] = useState<boolean>(false);
|
|
|
|
const [results, setResults] = useState<SearchResult[]>([]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (searchInput.current) {
|
|
|
|
if (
|
|
|
|
searchFocused &&
|
|
|
|
global.document.activeElement !== searchInput.current
|
|
|
|
) {
|
|
|
|
searchInput.current.focus();
|
|
|
|
} else if (
|
|
|
|
!searchFocused &&
|
|
|
|
global.document.activeElement === searchInput.current
|
|
|
|
) {
|
|
|
|
searchInput.current.blur();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, [searchFocused]);
|
|
|
|
|
|
|
|
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
|
setSearch(e.target.value);
|
2022-01-25 05:31:25 +00:00
|
|
|
if (fuse.current) {
|
|
|
|
const results: { item: Mod; refIndex: number }[] = fuse.current.search(
|
|
|
|
e.target.value
|
|
|
|
);
|
|
|
|
setResults(results);
|
|
|
|
}
|
2022-01-24 05:59:36 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const onChooseResult =
|
|
|
|
(item: Mod) =>
|
|
|
|
(e: React.MouseEvent<HTMLElement> | React.TouchEvent<HTMLElement>) => {
|
|
|
|
clearSelectedCell();
|
2022-01-25 05:31:25 +00:00
|
|
|
router.push({ query: { mod: item.id } });
|
2022-01-24 05:59:36 +00:00
|
|
|
setSearch("");
|
|
|
|
setResults([]);
|
|
|
|
setClickingResult(false);
|
|
|
|
setSearchFocused(false);
|
|
|
|
};
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div
|
|
|
|
className={`${styles["search-bar"]} ${
|
|
|
|
searchFocused ? styles["search-bar-focused"] : ""
|
|
|
|
}`}
|
|
|
|
>
|
|
|
|
<input
|
|
|
|
type="text"
|
|
|
|
placeholder="Search mods or cells…"
|
|
|
|
onChange={onChange}
|
|
|
|
onFocus={() => setSearchFocused(true)}
|
|
|
|
onBlur={() => {
|
|
|
|
if (!clickingResult) {
|
|
|
|
setSearch("");
|
|
|
|
setResults([]);
|
|
|
|
setSearchFocused(false);
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
value={search}
|
|
|
|
ref={searchInput}
|
2022-01-25 05:31:25 +00:00
|
|
|
disabled={!data}
|
2022-01-24 05:59:36 +00:00
|
|
|
/>
|
|
|
|
{results.length > 0 && (
|
|
|
|
<ul className={styles["search-results"]}>
|
|
|
|
{results.map((result) => (
|
|
|
|
<li
|
2022-01-25 05:31:25 +00:00
|
|
|
key={result.item.id}
|
2022-01-24 05:59:36 +00:00
|
|
|
onClick={onChooseResult(result.item)}
|
|
|
|
onTouchStart={() => setClickingResult(true)}
|
|
|
|
onMouseDown={() => setClickingResult(true)}
|
|
|
|
>
|
2022-01-25 05:31:25 +00:00
|
|
|
{result.item.name}
|
2022-01-24 05:59:36 +00:00
|
|
|
</li>
|
|
|
|
))}
|
|
|
|
</ul>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default SearchBar;
|