Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/insights/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"dnum": "catalog:",
"ioredis": "^5.7.0",
"lightweight-charts": "catalog:",
"match-sorter": "catalog:",
"motion": "catalog:",
"next": "catalog:",
"next-themes": "catalog:",
Expand Down
35 changes: 11 additions & 24 deletions apps/insights/src/components/PriceFeed/price-feed-select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import { SearchField } from "@pythnetwork/component-library/unstyled/SearchField
import { Select } from "@pythnetwork/component-library/unstyled/Select";
import { Input } from "@pythnetwork/component-library/unstyled/TextField";
import clsx from "clsx";
import { matchSorter } from "match-sorter";
import type { ReactNode } from "react";
import { useMemo, useState } from "react";
import { useCollator, useFilter } from "react-aria";

import styles from "./price-feed-select.module.scss";
import { AssetClassBadge } from "../AssetClassBadge";
Expand Down Expand Up @@ -56,7 +56,7 @@ type ResolvedPriceFeedSelect = {
symbol: string;
displaySymbol: string;
assetClass: string;
key: string;
key: string; // price_account
description: string;
icon: ReactNode;
}[];
Expand All @@ -66,29 +66,14 @@ const ResolvedPriceFeedSelect = ({
feeds,
...props
}: ResolvedPriceFeedSelect) => {
const collator = useCollator();
const filter = useFilter({ sensitivity: "base", usage: "search" });
const [search, setSearch] = useState("");
const filteredFeeds = useMemo(
const filteredAndSortedFeeds = useMemo(
() =>
search === ""
? feeds
: feeds.filter(
({ displaySymbol, assetClass, key }) =>
filter.contains(displaySymbol, search) ||
filter.contains(assetClass, search) ||
filter.contains(key, search),
),
[feeds, search, filter],
matchSorter(feeds, search, {
keys: ["displaySymbol", "symbol", "description", "key"],
}),
[feeds, search],
);
const sortedFeeds = useMemo(
() =>
filteredFeeds.toSorted((a, b) =>
collator.compare(a.displaySymbol, b.displaySymbol),
),
[filteredFeeds, collator],
);

return (
<PriceFeedSelectImpl
menu={
Expand All @@ -109,7 +94,7 @@ const ResolvedPriceFeedSelect = ({
</SearchField>
<Virtualizer layout={new ListLayout()}>
<ListBox
items={sortedFeeds}
items={filteredAndSortedFeeds}
className={styles.listbox ?? ""}
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={false}
Expand All @@ -120,7 +105,9 @@ const ResolvedPriceFeedSelect = ({
className={styles.priceFeed ?? ""}
href={`/price-feeds/${encodeURIComponent(symbol)}`}
data-is-first={
symbol === sortedFeeds[0]?.symbol ? "" : undefined
symbol === filteredAndSortedFeeds[0]?.symbol
? ""
: undefined
}
prefetch={false}
>
Expand Down
2 changes: 1 addition & 1 deletion apps/insights/src/components/Root/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,12 @@ const getPublishersForSearchDialog = async (cluster: Cluster) => {

const getFeedsForSearchDialog = async (cluster: Cluster) => {
const feeds = await getFeeds(cluster);

return feeds.map((feed) => ({
symbol: feed.symbol,
displaySymbol: feed.product.display_symbol,
assetClass: feed.product.asset_type,
description: feed.product.description,
priceAccount: feed.product.price_account,
icon: (
<PriceFeedIcon
assetClass={feed.product.asset_type}
Expand Down
82 changes: 32 additions & 50 deletions apps/insights/src/components/Root/search-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { SearchInput } from "@pythnetwork/component-library/SearchInput";
import { SingleToggleGroup } from "@pythnetwork/component-library/SingleToggleGroup";
import { Skeleton } from "@pythnetwork/component-library/Skeleton";
import {
Virtualizer,
ListLayout,
Virtualizer,
} from "@pythnetwork/component-library/Virtualizer";
import type { Button as UnstyledButton } from "@pythnetwork/component-library/unstyled/Button";
import {
Expand All @@ -20,16 +20,17 @@ import {
} from "@pythnetwork/component-library/unstyled/ListBox";
import { useDrawer } from "@pythnetwork/component-library/useDrawer";
import { useLogger } from "@pythnetwork/component-library/useLogger";
import { matchSorter } from "match-sorter";
import type { ReactNode } from "react";
import { useMemo, useCallback, useEffect, useState } from "react";
import { useIsSSR, useCollator, useFilter } from "react-aria";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useIsSSR } from "react-aria";

import styles from "./search-button.module.scss";
import { Cluster, ClusterToName } from "../../services/pyth";
import { AssetClassBadge } from "../AssetClassBadge";
import { PriceFeedTag } from "../PriceFeedTag";
import { PublisherTag } from "../PublisherTag";
import { Score } from "../Score";
import styles from "./search-button.module.scss";

const INPUTS = new Set(["input", "select", "button", "textarea"]);

Expand All @@ -50,6 +51,7 @@ type ResolvedSearchButtonProps = {
displaySymbol: string;
assetClass: string;
description: string;
priceAccount: string;
icon: ReactNode;
}[];
publishers: ({
Expand Down Expand Up @@ -170,58 +172,38 @@ const SearchDialogContents = ({
const logger = useLogger();
const [search, setSearch] = useState("");
const [type, setType] = useState<ResultType | "">("");
const collator = useCollator();
const filter = useFilter({ sensitivity: "base", usage: "search" });
const closeDrawer = useCallback(() => {
drawer.close().catch((error: unknown) => {
logger.error(error);
});
}, [drawer, logger]);
const results = useMemo(
() =>
[
...(type === ResultType.Publisher
? []
: // This is inefficient but Safari doesn't support `Iterator.filter`,
// see https://bugs.webkit.org/show_bug.cgi?id=248650
[...feeds.entries()]
.filter(([, { displaySymbol }]) =>
filter.contains(displaySymbol, search),
)
.map(([symbol, feed]) => ({
type: ResultType.PriceFeed as const,
id: symbol,
...feed,
}))),
...(type === ResultType.PriceFeed
? []
: publishers
.filter(
(publisher) =>
filter.contains(publisher.publisherKey, search) ||
(publisher.name && filter.contains(publisher.name, search)),
)
.map((publisher) => ({
type: ResultType.Publisher as const,
id: [
ClusterToName[publisher.cluster],
publisher.publisherKey,
].join(":"),
...publisher,
}))),
].sort((a, b) =>
collator.compare(
a.type === ResultType.PriceFeed
? a.displaySymbol
: (a.name ?? a.publisherKey),
b.type === ResultType.PriceFeed
? b.displaySymbol
: (b.name ?? b.publisherKey),
),
),
[feeds, publishers, collator, filter, search, type],
);

const results = useMemo(() => {
const filteredFeeds = matchSorter(feeds, search, {
keys: ["displaySymbol", "symbol", "description", "priceAccount"],
})
.entries()
.map(([symbol, feed]) => ({
type: ResultType.PriceFeed as const,
id: symbol,
...feed,
}));
const filteredPublishers = matchSorter(publishers, search, {
keys: ["publisherKey", "name"],
}).map((publisher) => ({
type: ResultType.Publisher as const,
id: [ClusterToName[publisher.cluster], publisher.publisherKey].join(":"),
...publisher,
}));

if (type === ResultType.PriceFeed) {
return [...filteredFeeds];
}
if (type === ResultType.Publisher) {
return [...filteredPublishers];
}
return [...filteredFeeds, ...filteredPublishers];
}, [feeds, publishers, search, type]);
return (
<div className={styles.searchDialogContents}>
<div className={styles.searchBar}>
Expand Down
Loading
Loading