Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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"],
}).map(({ symbol, ...feed }) => ({
type: ResultType.PriceFeed as const,
id: symbol,
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