First working prototype of contract tab

This commit is contained in:
Willian Mitsuda 2021-07-23 15:10:08 -03:00
parent 3842518e3e
commit 38f76e9e2b
8 changed files with 225 additions and 45 deletions

62
package-lock.json generated
View File

@ -30,15 +30,18 @@
"@types/react": "^17.0.19",
"@types/react-blockies": "^1.4.1",
"@types/react-dom": "^17.0.9",
"@types/react-highlight": "^0.12.3",
"@types/react-router-dom": "^5.1.8",
"chart.js": "^3.5.1",
"ethers": "^5.4.1",
"highlightjs-solidity": "^1.2.0",
"query-string": "^7.0.1",
"react": "^17.0.2",
"react-blockies": "^1.4.1",
"react-chartjs-2": "^3.0.4",
"react-dom": "^17.0.2",
"react-error-boundary": "^3.1.3",
"react-highlight": "^0.14.0",
"react-image": "^4.0.3",
"react-router-dom": "^5.2.1",
"react-scripts": "4.0.3",
@ -3103,6 +3106,14 @@
"@types/react": "*"
}
},
"node_modules/@types/react-highlight": {
"version": "0.12.3",
"resolved": "https://registry.npmjs.org/@types/react-highlight/-/react-highlight-0.12.3.tgz",
"integrity": "sha512-mfhuHdE3dUjvRv1lvZIvda2B+VW7rkG1ufnFLKbDcRUp/L73bGUmEuEfpnjgdLgeWYho88ahQZRcMSh9GsZA0g==",
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/react-router": {
"version": "5.1.15",
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.15.tgz",
@ -9207,6 +9218,19 @@
"version": "1.1.0",
"license": "MIT"
},
"node_modules/highlight.js": {
"version": "10.7.3",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
"integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==",
"engines": {
"node": "*"
}
},
"node_modules/highlightjs-solidity": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/highlightjs-solidity/-/highlightjs-solidity-1.2.0.tgz",
"integrity": "sha512-KXYcVzBRof3CBWHsxGffsSEAJF0YsPaOk1jgIYv2xSzrBSxkfNUJFXrlE2oZEWvYQKbPqLe4qprJyNbSDV+LZA=="
},
"node_modules/history": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
@ -14419,6 +14443,18 @@
"version": "6.0.9",
"license": "MIT"
},
"node_modules/react-highlight": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/react-highlight/-/react-highlight-0.14.0.tgz",
"integrity": "sha512-kWE+KXOXidS7SABhVopOgMnowbI3RAfeGZbnrduLNlWrYAED8sycL9l/Fvw3w0PFpIIawB7mRDnyhDcM/cIIGA==",
"dependencies": {
"highlight.js": "^10.5.0"
},
"peerDependencies": {
"react": "^15.0.0 || ^16.0.0 || ^17.0.0",
"react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0"
}
},
"node_modules/react-image": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/react-image/-/react-image-4.0.3.tgz",
@ -21465,6 +21501,14 @@
"@types/react": "*"
}
},
"@types/react-highlight": {
"version": "0.12.3",
"resolved": "https://registry.npmjs.org/@types/react-highlight/-/react-highlight-0.12.3.tgz",
"integrity": "sha512-mfhuHdE3dUjvRv1lvZIvda2B+VW7rkG1ufnFLKbDcRUp/L73bGUmEuEfpnjgdLgeWYho88ahQZRcMSh9GsZA0g==",
"requires": {
"@types/react": "*"
}
},
"@types/react-router": {
"version": "5.1.15",
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.15.tgz",
@ -25605,6 +25649,16 @@
"hex-color-regex": {
"version": "1.1.0"
},
"highlight.js": {
"version": "10.7.3",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
"integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="
},
"highlightjs-solidity": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/highlightjs-solidity/-/highlightjs-solidity-1.2.0.tgz",
"integrity": "sha512-KXYcVzBRof3CBWHsxGffsSEAJF0YsPaOk1jgIYv2xSzrBSxkfNUJFXrlE2oZEWvYQKbPqLe4qprJyNbSDV+LZA=="
},
"history": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
@ -29065,6 +29119,14 @@
"react-error-overlay": {
"version": "6.0.9"
},
"react-highlight": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/react-highlight/-/react-highlight-0.14.0.tgz",
"integrity": "sha512-kWE+KXOXidS7SABhVopOgMnowbI3RAfeGZbnrduLNlWrYAED8sycL9l/Fvw3w0PFpIIawB7mRDnyhDcM/cIIGA==",
"requires": {
"highlight.js": "^10.5.0"
}
},
"react-image": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/react-image/-/react-image-4.0.3.tgz",

View File

@ -25,15 +25,18 @@
"@types/react": "^17.0.19",
"@types/react-blockies": "^1.4.1",
"@types/react-dom": "^17.0.9",
"@types/react-highlight": "^0.12.3",
"@types/react-router-dom": "^5.1.8",
"chart.js": "^3.5.1",
"ethers": "^5.4.1",
"highlightjs-solidity": "^1.2.0",
"query-string": "^7.0.1",
"react": "^17.0.2",
"react-blockies": "^1.4.1",
"react-chartjs-2": "^3.0.4",
"react-dom": "^17.0.2",
"react-error-boundary": "^3.1.3",
"react-highlight": "^0.14.0",
"react-image": "^4.0.3",
"react-router-dom": "^5.2.1",
"react-scripts": "4.0.3",

View File

@ -1,5 +1,11 @@
import React, { useState, useEffect, useMemo, useContext } from "react";
import { useParams, useLocation, useHistory } from "react-router-dom";
import {
useParams,
useLocation,
useHistory,
Switch,
Route,
} from "react-router-dom";
import { BlockTag } from "@ethersproject/abstract-provider";
import { getAddress, isAddress } from "@ethersproject/address";
import queryString from "query-string";
@ -8,6 +14,9 @@ import StandardFrame from "./StandardFrame";
import StandardSubtitle from "./StandardSubtitle";
import Copy from "./components/Copy";
import ContentFrame from "./ContentFrame";
import TabGroup from "./components/TabGroup";
import Tab from "./components/Tab";
import Contract from "./address/Contract";
import UndefinedPageControl from "./search/UndefinedPageControl";
import ResultHeader from "./search/ResultHeader";
import PendingResults from "./search/PendingResults";
@ -194,59 +203,79 @@ const AddressTransactions: React.FC = () => {
)}
</div>
</StandardSubtitle>
<ContentFrame>
<div className="flex justify-between items-baseline py-3">
<div className="text-sm text-gray-500">
{page === undefined ? (
<>Waiting for search results...</>
) : (
<>{page.length} transactions on this page</>
)}
</div>
<UndefinedPageControl
address={params.addressOrName}
isFirst={controller?.isFirst}
isLast={controller?.isLast}
prevHash={page ? page[0].hash : ""}
nextHash={page ? page[page.length - 1].hash : ""}
disabled={controller === undefined}
/>
</div>
<ResultHeader
feeDisplay={feeDisplay}
feeDisplayToggler={feeDisplayToggler}
/>
{controller ? (
<SelectionContext.Provider value={selectionCtx}>
{controller.getPage().map((tx) => (
<TransactionItem
key={tx.hash}
tx={tx}
ensCache={reverseCache}
selectedAddress={checksummedAddress}
feeDisplay={feeDisplay}
priceMap={priceMap}
/>
))}
<TabGroup>
<Tab href={`/address/${checksummedAddress}`}>Overview</Tab>
<Tab href={`/address/${checksummedAddress}/contract`}>
Contract
</Tab>
</TabGroup>
<Switch>
<Route path="/address/:addressOrName" exact>
<ContentFrame tabs>
<div className="flex justify-between items-baseline py-3">
<div className="text-sm text-gray-500">
{page !== undefined && (
{page === undefined ? (
<>Waiting for search results...</>
) : (
<>{page.length} transactions on this page</>
)}
</div>
<UndefinedPageControl
address={params.addressOrName}
isFirst={controller.isFirst}
isLast={controller.isLast}
isFirst={controller?.isFirst}
isLast={controller?.isLast}
prevHash={page ? page[0].hash : ""}
nextHash={page ? page[page.length - 1].hash : ""}
disabled={controller === undefined}
/>
</div>
</SelectionContext.Provider>
) : (
<PendingResults />
)}
</ContentFrame>
<ResultHeader
feeDisplay={feeDisplay}
feeDisplayToggler={feeDisplayToggler}
/>
{controller ? (
<SelectionContext.Provider value={selectionCtx}>
{controller.getPage().map((tx) => (
<TransactionItem
key={tx.hash}
tx={tx}
ensCache={reverseCache}
selectedAddress={checksummedAddress}
feeDisplay={feeDisplay}
priceMap={priceMap}
/>
))}
<div className="flex justify-between items-baseline py-3">
<div className="text-sm text-gray-500">
{page === undefined ? (
<>Waiting for search results...</>
) : (
<>{page.length} transactions on this page</>
)}
</div>
<UndefinedPageControl
address={params.addressOrName}
isFirst={controller?.isFirst}
isLast={controller?.isLast}
prevHash={page ? page[0].hash : ""}
nextHash={page ? page[page.length - 1].hash : ""}
disabled={controller === undefined}
/>
</div>
<ResultHeader
feeDisplay={feeDisplay}
feeDisplayToggler={feeDisplayToggler}
/>
</SelectionContext.Provider>
) : (
<PendingResults />
)}
</ContentFrame>
</Route>
<Route path="/address/:addressOrName/contract" exact>
<Contract checksummedAddress={checksummedAddress} />
</Route>
</Switch>
</>
)
)}

View File

@ -3,6 +3,7 @@ import { Route, Switch, useParams } from "react-router-dom";
import StandardFrame from "./StandardFrame";
import StandardSubtitle from "./StandardSubtitle";
import ContentFrame from "./ContentFrame";
import TabGroup from "./components/TabGroup";
import Tab from "./components/Tab";
import Details from "./transaction/Details";
import Logs from "./transaction/Logs";
@ -55,14 +56,14 @@ const Transaction: React.FC = () => {
)}
{txData && (
<SelectionContext.Provider value={selectionCtx}>
<div className="flex space-x-2 border-l border-r border-t rounded-t-lg bg-white">
<TabGroup>
<Tab href={`/tx/${txhash}`}>Overview</Tab>
{txData.confirmedData?.blockNumber !== undefined && (
<Tab href={`/tx/${txhash}/logs`}>
Logs{txData && ` (${txData.confirmedData?.logs?.length ?? 0})`}
</Tab>
)}
</div>
</TabGroup>
<Switch>
<Route path="/tx/:txhash/" exact>
<Details

76
src/address/Contract.tsx Normal file
View File

@ -0,0 +1,76 @@
import React, { useState, useEffect } from "react";
import ContentFrame from "../ContentFrame";
import Highlight from "react-highlight";
import "highlight.js/styles/stackoverflow-light.css";
import hljs from "highlight.js";
import hljsDefineSolidity from "highlightjs-solidity";
hljsDefineSolidity(hljs);
hljs.initHighlightingOnLoad();
type ContractProps = {
checksummedAddress: string;
};
const Contract: React.FC<ContractProps> = ({ checksummedAddress }) => {
const [sources, setSources] = useState<
{ [fileName: string]: any } | undefined | null
>(undefined);
useEffect(() => {
if (!checksummedAddress) {
return;
}
const fetchMetadata = async () => {
try {
const result = await fetch(
`https://repo.sourcify.dev/contracts/full_match/1/${checksummedAddress}/metadata.json`
);
if (result.ok) {
const json = await result.json();
console.log(json);
setSources(json.sources);
setSelected(Object.keys(json.sources)[0]);
} else {
setSources(null);
}
} catch (err) {
console.error(err);
setSources(null);
}
};
fetchMetadata();
}, [checksummedAddress]);
const [selected, setSelected] = useState<string>();
return (
<ContentFrame tabs>
<div className="py-5">
{sources === null && (
<span>Couldn't find contract metadata in Sourcify repository.</span>
)}
{sources !== undefined && sources !== null && (
<>
{Object.entries(sources).map(([k]) => (
<button
className={`border-b-2 border-transparent rounded-t text-sm px-2 py-1 bg-gray-200 text-gray-500 ${
selected === k ? "border-orange-300 font-bold" : ""
}`}
>
{k}
</button>
))}
{selected && (
<Highlight className="w-full h-full border focus:outline-none font-code text-base">
{sources[selected].content}
</Highlight>
)}
</>
)}
</div>
</ContentFrame>
);
};
export default React.memo(Contract);

View File

@ -0,0 +1,7 @@
const TabGroup: React.FC = ({ children }) => (
<div className="flex space-x-2 border-l border-r border-t rounded-t-lg bg-white">
{children}
</div>
);
export default TabGroup;

View File

@ -1,2 +1,3 @@
/// <reference types="react-scripts" />
declare module "use-keyboard-shortcut";
declare module "highlightjs-solidity";

View File

@ -27,6 +27,7 @@ module.exports = {
data: ["Roboto Mono"],
balance: ["Fira Code"],
blocknum: ["Roboto"],
code: ["Fira Code"],
},
borderColor: {
skin: {