import { Combobox, ComboboxInput, ComboboxOption, ComboboxOptions, } from "@headlessui/react"; import { ArrowPathIcon } from "@heroicons/react/24/outline"; import base58 from "bs58"; import clsx from "clsx"; import Image from "next/image"; import { type ChangeEvent, type Dispatch, type SetStateAction, useState, useCallback, useMemo, useEffect, } from "react"; import { type Parameter, PLACEHOLDERS, isValid, getValidationError, ParameterType, } from "./parameter"; import { type PriceFeed, PriceFeedListContextType, usePriceFeedList, } from "../../use-price-feed-list"; import { InlineLink } from "../InlineLink"; import { Input } from "../Input"; import { Markdown } from "../Markdown"; type ParameterProps = { spec: Parameter; value: string | undefined; setParamValues: Dispatch< SetStateAction>> >; }; export const ParameterInput = ( props: ParameterProps, ) => { switch (props.spec.type) { case ParameterType.PriceFeedId: case ParameterType.PriceFeedIdArray: { return ; } default: { return ; } } }; const PriceFeedIdInput = ({ spec, value, setParamValues, }: ParameterProps) => { const { validationError, internalValue, onChange } = useParameterInput( spec, value, setParamValues, ); const { selectedPriceFeed, onSelectPriceFeed, priceFeedList } = usePriceFeedSelector(internalValue, onChange); const onChangeInput = useCallback( (event: ChangeEvent) => { onChange(event.target.value); }, [onChange], ); return ( selectedPriceFeed ? `${selectedPriceFeed.name} (${selectedPriceFeed.feedId})` : internalValue } onChange={onChangeInput} validationError={validationError} label={spec.name} description={{spec.description}} placeholder={PLACEHOLDERS[spec.type]} required={true} />
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
{ e.preventDefault(); }} > See all price feed IDs on{" "} the reference page
); }; type PriceFeedListOptionsProps = { priceFeedList: ReturnType["priceFeedList"]; }; const PriceFeedListOptions = ({ priceFeedList }: PriceFeedListOptionsProps) => { if (priceFeedList.type === PriceFeedListContextType.Loaded) { return priceFeedList.list.length === 0 ? (
No matching price feeds
) : ( {({ option }: { option: PriceFeed }) => { const { feedId, name, description } = option; return (
{name}
{description}
{feedId}
); }}
); } else { return (
); } }; type PriceFeedIconProps = { name: string; }; const PriceFeedIcon = ({ name }: PriceFeedIconProps) => { const [isLoaded, setIsLoaded] = useState(false); const setLoaded = useCallback(() => { setIsLoaded(true); }, [setIsLoaded]); const icon = useMemo(() => { const nameParts = name.split("."); return nameParts.at(-1)?.split("/")[0]?.toLowerCase() ?? "generic"; }, [name]); return (
); }; const DefaultParameterInput = ({ spec, value, setParamValues, }: ParameterProps) => { const { validationError, internalValue, onChange } = useParameterInput( spec, value, setParamValues, ); const onChangeInput = useCallback( (event: ChangeEvent) => { onChange(event.target.value); }, [onChange], ); return ( {spec.description}} placeholder={PLACEHOLDERS[spec.type]} required={true} value={internalValue} onChange={onChangeInput} /> ); }; const useParameterInput = ( spec: Parameter, value: string | undefined, setParamValues: Dispatch< SetStateAction>> >, ) => { const [internalValue, setInternalValue] = useState(value ?? ""); const validationError = useMemo( () => (internalValue ? getValidationError(spec, internalValue) : undefined), [internalValue, spec], ); const onChange = useCallback( (value: string) => { setInternalValue(value); setParamValues((paramValues) => ({ ...paramValues, [spec.name]: value === "" || !isValid(spec, value) ? undefined : value, })); }, [setParamValues, spec], ); useEffect(() => { if (value) { setInternalValue(value); } }, [value]); return { internalValue, validationError, onChange }; }; const usePriceFeedSelector = ( internalValue: string, onChange: (value: string) => void, ) => { const priceFeedList = usePriceFeedList(); const sortedPriceFeedListWithHexIds = useMemo(() => { return priceFeedList.type === PriceFeedListContextType.Loaded ? priceFeedList.priceFeedList .map((feed) => ({ ...feed, feedId: pubKeyToHex(feed.feedId) })) .sort((a, b) => a.name.localeCompare(b.name)) : []; }, [priceFeedList]); const selectedPriceFeed = useMemo( () => sortedPriceFeedListWithHexIds.find( ({ feedId }) => feedId === internalValue, // eslint-disable-next-line unicorn/no-null ) ?? null, [sortedPriceFeedListWithHexIds, internalValue], ); const onSelectPriceFeed = useCallback( (priceFeed: PriceFeed | null) => { if (priceFeed) { onChange(priceFeed.feedId); } }, [onChange], ); const filteredPriceFeedList = useMemo(() => { if (selectedPriceFeed === null) { const query = internalValue.toLowerCase(); return sortedPriceFeedListWithHexIds.filter( ({ name, description }) => name.toLowerCase().includes(query) || description.toLowerCase().includes(query), ); } else { return sortedPriceFeedListWithHexIds; } }, [selectedPriceFeed, internalValue, sortedPriceFeedListWithHexIds]); const transformedPriceFeedList = useMemo( () => priceFeedList.type === PriceFeedListContextType.Loaded ? { type: PriceFeedListContextType.Loaded as const, list: filteredPriceFeedList, } : priceFeedList, [priceFeedList, filteredPriceFeedList], ); return { selectedPriceFeed, onSelectPriceFeed, priceFeedList: transformedPriceFeedList, }; }; const pubKeyToHex = (pubKey: string) => [ "0x", ...Array.from(base58.decode(pubKey), (byte) => byte.toString(16).padStart(2, "0"), ), ].join("");