diff --git a/src/AddressTransactions.tsx b/src/AddressTransactions.tsx index 45a07ca..e3a23a0 100644 --- a/src/AddressTransactions.tsx +++ b/src/AddressTransactions.tsx @@ -31,8 +31,8 @@ import { useENSCache } from "./useReverseCache"; import { useFeeToggler } from "./search/useFeeToggler"; import { SelectionContext, useSelection } from "./useSelection"; import { useMultipleETHUSDOracle } from "./usePriceOracle"; +import { useAppConfigContext } from "./useAppConfig"; import { useSourcify } from "./useSourcify"; -import { SourcifySource } from "./url"; type BlockParams = { addressOrName: string; @@ -180,9 +180,7 @@ const AddressTransactions: React.FC = () => { const [feeDisplay, feeDisplayToggler] = useFeeToggler(); const selectionCtx = useSelection(); - const [sourcifySource, setSourcifySource] = useState( - SourcifySource.IPFS_IPNS - ); + const { sourcifySource } = useAppConfigContext(); const rawMetadata = useSourcify( checksummedAddress, provider?.network.chainId, @@ -316,8 +314,6 @@ const AddressTransactions: React.FC = () => { diff --git a/src/App.tsx b/src/App.tsx index 4f1333d..648c58f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import React, { Suspense } from "react"; +import React, { Suspense, useMemo, useState } from "react"; import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; import WarningHeader from "./WarningHeader"; import Home from "./Home"; @@ -9,6 +9,8 @@ import London from "./special/london/London"; import Footer from "./Footer"; import { ConnectionStatus } from "./types"; import { RuntimeContext, useRuntime } from "./useRuntime"; +import { AppConfig, AppConfigContext } from "./useAppConfig"; +import { SourcifySource } from "./url"; const Block = React.lazy(() => import("./Block")); const BlockTransactions = React.lazy(() => import("./BlockTransactions")); @@ -17,6 +19,15 @@ const Transaction = React.lazy(() => import("./Transaction")); const App = () => { const runtime = useRuntime(); + const [sourcifySource, setSourcifySource] = useState( + SourcifySource.IPFS_IPNS + ); + const appConfig = useMemo((): AppConfig => { + return { + sourcifySource, + setSourcifySource, + }; + }, [sourcifySource, setSourcifySource]); return ( LOADING}> @@ -41,21 +52,23 @@ const App = () => { -
- - <Route path="/block/:blockNumberOrHash" exact> - <Block /> - </Route> - <Route path="/block/:blockNumber/txs" exact> - <BlockTransactions /> - </Route> - <Route path="/tx/:txhash"> - <Transaction /> - </Route> - <Route path="/address/:addressOrName/:direction?"> - <AddressTransactions /> - </Route> - </div> + <AppConfigContext.Provider value={appConfig}> + <div className="mb-auto"> + <Title /> + <Route path="/block/:blockNumberOrHash" exact> + <Block /> + </Route> + <Route path="/block/:blockNumber/txs" exact> + <BlockTransactions /> + </Route> + <Route path="/tx/:txhash"> + <Transaction /> + </Route> + <Route path="/address/:addressOrName/:direction?"> + <AddressTransactions /> + </Route> + </div> + </AppConfigContext.Provider> </Route> </Switch> </Router> diff --git a/src/SourcifyMenu.tsx b/src/SourcifyMenu.tsx new file mode 100644 index 0000000..36f43a5 --- /dev/null +++ b/src/SourcifyMenu.tsx @@ -0,0 +1,73 @@ +import React from "react"; +import { Menu } from "@headlessui/react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faBars } from "@fortawesome/free-solid-svg-icons/faBars"; +import { SourcifySource } from "./url"; +import { useAppConfigContext } from "./useAppConfig"; + +const SourcifyMenu: React.FC = () => { + const { sourcifySource, setSourcifySource } = useAppConfigContext(); + + return ( + <Menu> + <div className="relative self-stretch"> + <Menu.Button className="w-full h-full flex justify-center items-center space-x-2 text-sm border rounded px-2 py-1"> + <FontAwesomeIcon icon={faBars} size="1x" /> + </Menu.Button> + <Menu.Items className="absolute right-0 mt-1 border p-1 rounded-b bg-white flex flex-col text-sm min-w-max"> + <div className="px-2 py-1 text-xs border-b border-gray-300"> + Sourcify Datasource + </div> + <SourcifyMenuItem + checked={sourcifySource === SourcifySource.IPFS_IPNS} + onClick={() => setSourcifySource(SourcifySource.IPFS_IPNS)} + > + Resolve IPNS + </SourcifyMenuItem> + <SourcifyMenuItem + checked={sourcifySource === SourcifySource.CENTRAL_SERVER} + onClick={() => setSourcifySource(SourcifySource.CENTRAL_SERVER)} + > + Sourcify Servers + </SourcifyMenuItem> + <SourcifyMenuItem + checked={sourcifySource === SourcifySource.CUSTOM_SNAPSHOT_SERVER} + onClick={() => + setSourcifySource(SourcifySource.CUSTOM_SNAPSHOT_SERVER) + } + > + Local Snapshot + </SourcifyMenuItem> + </Menu.Items> + </div> + </Menu> + ); +}; + +type SourcifyMenuItemProps = { + checked?: boolean; + onClick: () => void; +}; + +const SourcifyMenuItem: React.FC<SourcifyMenuItemProps> = ({ + checked = false, + onClick, + children, +}) => ( + <Menu.Item> + {({ active }) => ( + <button + className={`text-sm text-left px-2 py-1 ${ + active ? "border-orange-200 text-gray-500" : "text-gray-400" + } transition-transform transition-colors duration-75 ${ + checked ? "text-gray-900" : "" + }`} + onClick={onClick} + > + {children} + </button> + )} + </Menu.Item> +); + +export default React.memo(SourcifyMenu); diff --git a/src/Title.tsx b/src/Title.tsx index 25b408d..e4c5f47 100644 --- a/src/Title.tsx +++ b/src/Title.tsx @@ -5,6 +5,7 @@ import { faQrcode } from "@fortawesome/free-solid-svg-icons/faQrcode"; import useKeyboardShortcut from "use-keyboard-shortcut"; import PriceBox from "./PriceBox"; import CameraScanner from "./search/CameraScanner"; +import SourcifyMenu from "./SourcifyMenu"; import { RuntimeContext } from "./useRuntime"; const Title: React.FC = () => { @@ -82,6 +83,7 @@ const Title: React.FC = () => { Search </button> </form> + <SourcifyMenu /> </div> </div> </> diff --git a/src/Transaction.tsx b/src/Transaction.tsx index a412d0b..29c7208 100644 --- a/src/Transaction.tsx +++ b/src/Transaction.tsx @@ -11,8 +11,8 @@ import { RuntimeContext } from "./useRuntime"; import { SelectionContext, useSelection } from "./useSelection"; import { useInternalOperations, useTxData } from "./useErigonHooks"; import { useETHUSDOracle } from "./usePriceOracle"; +import { useAppConfigContext } from "./useAppConfig"; import { useSourcify, useTransactionDescription } from "./useSourcify"; -import { SourcifySource } from "./url"; type TransactionParams = { txhash: string; @@ -46,10 +46,11 @@ const Transaction: React.FC = () => { txData?.confirmedData?.blockNumber ); + const { sourcifySource } = useAppConfigContext(); const metadata = useSourcify( txData?.to, provider?.network.chainId, - SourcifySource.CENTRAL_SERVER // TODO: use dynamic selector + sourcifySource ); const txDesc = useTransactionDescription(metadata, txData); diff --git a/src/address/Contract.tsx b/src/address/Contract.tsx index eece5fd..7ddfe56 100644 --- a/src/address/Contract.tsx +++ b/src/address/Contract.tsx @@ -3,9 +3,9 @@ import { Light as SyntaxHighlighter } from "react-syntax-highlighter"; import hljs from "highlight.js"; import docco from "react-syntax-highlighter/dist/esm/styles/hljs/docco"; import { useContract } from "../useSourcify"; -import { SourcifySource } from "../url"; import hljsDefineSolidity from "highlightjs-solidity"; +import { useAppConfigContext } from "../useAppConfig"; hljsDefineSolidity(hljs); type ContractProps = { @@ -13,7 +13,6 @@ type ContractProps = { networkId: number; filename: string; source: any; - sourcifySource: SourcifySource; }; const Contract: React.FC<ContractProps> = ({ @@ -21,8 +20,8 @@ const Contract: React.FC<ContractProps> = ({ networkId, filename, source, - sourcifySource, }) => { + const { sourcifySource } = useAppConfigContext(); const content = useContract( checksummedAddress, networkId, diff --git a/src/address/Contracts.tsx b/src/address/Contracts.tsx index 4d322c3..0745d73 100644 --- a/src/address/Contracts.tsx +++ b/src/address/Contracts.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect, useContext, Fragment } from "react"; import { commify } from "@ethersproject/units"; -import { Menu, RadioGroup } from "@headlessui/react"; +import { Menu } from "@headlessui/react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown"; import ContentFrame from "../ContentFrame"; @@ -11,21 +11,16 @@ import Contract from "./Contract"; import { RuntimeContext } from "../useRuntime"; import { Metadata } from "../useSourcify"; import ExternalLink from "../components/ExternalLink"; -import { openInRemixURL, SourcifySource } from "../url"; -import RadioButton from "./RadioButton"; +import { openInRemixURL } from "../url"; type ContractsProps = { checksummedAddress: string; rawMetadata: Metadata | null | undefined; - sourcifySource: SourcifySource; - setSourcifySource: (sourcifySource: SourcifySource) => void; }; const Contracts: React.FC<ContractsProps> = ({ checksummedAddress, rawMetadata, - sourcifySource, - setSourcifySource, }) => { const { provider } = useContext(RuntimeContext); @@ -39,21 +34,6 @@ const Contracts: React.FC<ContractsProps> = ({ return ( <ContentFrame tabs> - <InfoRow title="Sourcify integration"> - <RadioGroup value={sourcifySource} onChange={setSourcifySource}> - <div className="flex space-x-2"> - <RadioButton value={SourcifySource.IPFS_IPNS}> - Resolve IPNS @localhost:8080 gateway - </RadioButton> - <RadioButton value={SourcifySource.CENTRAL_SERVER}> - Sourcify Servers - </RadioButton> - <RadioButton value={SourcifySource.CUSTOM_SNAPSHOT_SERVER}> - Local Snapshot @localhost:3006 - </RadioButton> - </div> - </RadioGroup> - </InfoRow> {rawMetadata && ( <> <InfoRow title="Language"> @@ -145,7 +125,6 @@ const Contracts: React.FC<ContractsProps> = ({ networkId={provider!.network.chainId} filename={selected} source={rawMetadata.sources[selected]} - sourcifySource={sourcifySource} /> )} </div> diff --git a/src/address/RadioButton.tsx b/src/address/RadioButton.tsx deleted file mode 100644 index e93fad3..0000000 --- a/src/address/RadioButton.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from "react"; -import { RadioGroup } from "@headlessui/react"; -import { SourcifySource } from "../url"; - -type RadioButtonProps = { - value: SourcifySource; -}; - -const RadioButton: React.FC<RadioButtonProps> = ({ value, children }) => ( - <RadioGroup.Option - className={({ checked }) => - `border rounded px-2 py-1 cursor-pointer ${ - checked - ? "bg-blue-400 hover:bg-blue-500 text-white" - : "hover:bg-gray-200" - }` - } - value={value} - > - {children} - </RadioGroup.Option> -); - -export default RadioButton; diff --git a/src/transaction/Logs.tsx b/src/transaction/Logs.tsx index a274c90..3990a24 100644 --- a/src/transaction/Logs.tsx +++ b/src/transaction/Logs.tsx @@ -3,8 +3,8 @@ import { Interface } from "@ethersproject/abi"; import ContentFrame from "../ContentFrame"; import LogEntry from "./LogEntry"; import { TransactionData } from "../types"; +import { useAppConfigContext } from "../useAppConfig"; import { Metadata, useMultipleMetadata } from "../useSourcify"; -import { SourcifySource } from "../url"; type LogsProps = { txData: TransactionData; @@ -22,6 +22,7 @@ const Logs: React.FC<LogsProps> = ({ txData, metadata }) => { return md; }, [txData.to, metadata]); + const { sourcifySource } = useAppConfigContext(); const logAddresses = useMemo( () => txData.confirmedData?.logs.map((l) => l.address) ?? [], [txData] @@ -30,7 +31,7 @@ const Logs: React.FC<LogsProps> = ({ txData, metadata }) => { baseMetadatas, logAddresses, 1, - SourcifySource.CENTRAL_SERVER // TODO: use dynamic selector + sourcifySource ); const logDesc = useMemo(() => { if (!txData) { diff --git a/src/useAppConfig.ts b/src/useAppConfig.ts new file mode 100644 index 0000000..56007e0 --- /dev/null +++ b/src/useAppConfig.ts @@ -0,0 +1,13 @@ +import React, { useContext } from "react"; +import { SourcifySource } from "./url"; + +export type AppConfig = { + sourcifySource: SourcifySource; + setSourcifySource: (newSourcifySource: SourcifySource) => void; +}; + +export const AppConfigContext = React.createContext<AppConfig>(undefined!); + +export const useAppConfigContext = () => { + return useContext(AppConfigContext); +};