import { Icon, IconButton, PlainInput } from "@yaakapp-internal/ui";
import { useAtom, useAtomValue, useSetAtom, useStore } from "jotai";
import { memo, type RefObject, useEffect, useRef } from "react";
import {
allTrafficTotalAtom,
lockedAtom,
searchTermsAtom,
searchTextAtom,
searchTextVersionAtom,
setSearchTextAtom,
} from "../lib/store";
/** How long typing settles before the search runs */
const SEARCH_DEBOUNCE_MS = 200;
/**
* The header's search over every flow in the database, by URL, method, status and content type,
* and the `@app`, `@host` and `@path` terms the sidebar writes. Its placeholder counts the flows.
* ⌘F focuses it and Escape clears it.
*/
export const SearchBar = memo(function SearchBar() {
const [text, setText] = useAtom(searchTextAtom);
const setTerms = useSetAtom(searchTermsAtom);
const setSearchText = useSetAtom(setSearchTextAtom);
const version = useAtomValue(searchTextVersionAtom);
// Nothing to search while captured traffic is locked.
const locked = useAtomValue(lockedAtom) != null;
// Read, not subscribed: `PlaceholderCount` keeps it current without re-rendering the field.
const total = useStore().get(allTrafficTotalAtom);
// PlainInput hands back its as the ref
const inputRef = useRef(null);
useEffect(() => {
const timer = setTimeout(() => setTerms(text), SEARCH_DEBOUNCE_MS);
return () => clearTimeout(timer);
}, [text, setTerms]);
useEffect(() => {
const onKeyDown = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey && e.key.toLowerCase() === "f") {
e.preventDefault();
inputRef.current?.focus();
inputRef.current?.select();
}
};
window.addEventListener("keydown", onKeyDown);
return () => window.removeEventListener("keydown", onKeyDown);
}, []);
// The field is replaced to take a value from outside, so focus moves to the new one.
const clear = () => {
setSearchText("");
requestAnimationFrame(() => inputRef.current?.focus());
};
return (
{
if (e.key === "Escape" && text !== "") {
e.stopPropagation();
clear();
}
}}
leftSlot={
<>
>
}
rightSlot={
text === "" ? (
⌘F
) : (
)
}
/>
);
});
/**
* Keeps the placeholder's count current as flows arrive, alone, so new traffic re-renders this
* and not the whole field
*/
function PlaceholderCount({ inputRef }: { inputRef: RefObject }) {
const total = useAtomValue(allTrafficTotalAtom);
useEffect(() => {
if (inputRef.current != null) inputRef.current.placeholder = placeholderFor(total);
}, [inputRef, total]);
return null;
}
/** "Search 4,102 flows", the title and count that sat in the header's middle */
function placeholderFor(total: number): string {
if (total === 0) return "Search flows";
return `Search ${total.toLocaleString()} ${total === 1 ? "flow" : "flows"}`;
}