import { APIFetchV1 } from "util/api";
import { ConditionalLeadingSpace, PREVENT_DEFAULT } from "util/misc";
import Divider from "components/util/Divider";
import Icon from "components/util/Icon";
import Loading from "components/util/Loading";
import { TachiConfig } from "lib/config";
import React, { useEffect, useMemo, useRef, useState } from "react";
import { Link, useLocation } from "react-router-dom";
import { Game, GetGameConfig, integer, UserDocument, SongDocument, Playtype } from "tachi-common";
import { GamePT, JustChildren } from "types/react";
import QuickTooltip from "../misc/QuickTooltip";
interface SearchReturns {
users?: UserDocument[];
songs: (SongDocument & { __textScore: number; game: Game })[];
}
function SearchResult({
children,
link,
tabIndex,
}: JustChildren & { link: string; tabIndex?: integer }) {
return (
{children}
);
}
function GPTSearchResult({
user,
game,
playtype,
tabIndex,
}: {
user: UserDocument;
tabIndex: integer;
} & GamePT) {
const currentLoc = window.location.href.split(`games/${game}/${playtype}`)[1];
return (
{user.username}
{user.status}
);
}
function SearchResults({ results }: { results: SearchReturns }) {
if (!results.users?.length && !results.songs.length) {
return (
Found nothing :(
);
}
// useParams doesn't do what i think it does, so...
// here's a hack.
const params = useLocation<{ game?: Game; playtype?: Playtype }>();
const { game, playtype, user } = useMemo(() => {
const regexp = `/games/(${TachiConfig.games.join("|")})/[^/]*/?`;
if (params.pathname.match(new RegExp(regexp, "u"))) {
const hack = params.pathname.split("/games/")[1].split("/");
return {
game: hack[0] as Game,
playtype: hack[1] as Playtype,
user: params.pathname.match(/\/dashboard\/users\/(.*?)\//u)?.[1],
};
}
return { game: null, playtype: null };
}, [params.pathname]);
return (
{results?.users?.length ? (
<>
Users
{results.users.map((u) =>
game && playtype && user ? (
) : (
{u.username}
{u.status}
)
)}
>
) : (
<>>
)}
{results?.songs.length ? (
<>
Songs
{results.songs.map((s) => (
{FormatSongTitle(s.game, s)}
{GetGameConfig(s.game).name}
))}
>
) : (
<>>
)}
);
}
function FormatSongTitle(game: Game, song: SongDocument) {
if (game === "bms" || game === "pms") {
const s = song as SongDocument<"bms" | "pms">;
return `${song.title}${ConditionalLeadingSpace(s.data.subtitle)}${ConditionalLeadingSpace(
s.data.tableString
)}`;
}
if (game === "itg") {
const s = song as SongDocument<"itg">;
return `${song.title}${ConditionalLeadingSpace(s.data.subtitle)}`;
}
return song.title;
}
export default function SearchBar() {
const [show, setShow] = useState(false);
const [search, setSearch] = useState("");
const [results, setResults] = useState(null);
const [lastTimeout, setLastTimeout] = useState(null);
const ref = useRef(null);
const inputRef = useRef(null);
// onclick outside
useEffect(() => {
function clickOutside(event: MouseEvent) {
// @ts-expect-error lazy
if (ref.current && !ref.current.contains(event.target)) {
setShow(false);
}
}
document.addEventListener("mousedown", clickOutside);
return () => {
document.removeEventListener("mousedown", clickOutside);
};
}, [ref]);
useEffect(() => {
setShow(search !== "");
}, [search]);
// debouncer result-getter
function UpdateSearch(event: React.ChangeEvent) {
setSearch(event.target.value);
if (lastTimeout !== null) {
clearTimeout(lastTimeout);
}
const closureSearch = event.target.value;
// compatibility note - we use window here to specify to typescript
// that this is NOT the node version of setTimeout() (which does not return a number).
const handle = window.setTimeout(() => {
const searches = [];
searches.push(
APIFetchV1(`/search?search=${encodeURIComponent(closureSearch)}`)
);
if (TachiConfig.type !== "ktchi") {
searches.push(
APIFetchV1(
`/search/chart-hash?search=${encodeURIComponent(closureSearch)}`
)
);
}
Promise.all(searches).then((results) => {
const setValue: SearchReturns = {
users: [],
songs: [],
};
for (const result of results) {
if (result.success === false) {
console.error(result);
return;
}
setValue.songs.push(...result.body.songs);
setValue.users!.push(...(result.body.users ?? []));
}
setResults(setValue);
});
}, 250);
setLastTimeout(handle);
}
return (
);
}