Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better transaction result formatting in debug page #853

Merged
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ae80b00
feat: add toggleable number display for tx result
ByteAtATime May 27, 2024
f072135
feat: don't quote string tx result if it's a hex value
ByteAtATime May 27, 2024
8884f09
feat: display tuple elements separately
ByteAtATime May 27, 2024
490e60f
fix: allow tx result to scroll
ByteAtATime May 27, 2024
c7cfd04
feat: pretty format structs
ByteAtATime May 27, 2024
37df0ec
fix: TxReceipt to accept txResult as key in props
technophile-04 May 29, 2024
946900a
refactor: remove `asText` parameter in displayTxResult
ByteAtATime May 29, 2024
4a25c0d
refactor: rename TupleDisplay to ArrayDisplay
ByteAtATime May 29, 2024
931ad37
fix: convert slash to ArrowsRightLeftIcon
ByteAtATime May 29, 2024
23861a1
refactor: extract ObjectFieldDisplay from array/struct displays
ByteAtATime May 31, 2024
b66e87d
fix: fix address too large issue
ByteAtATime May 31, 2024
81a5010
feat: add a little gap so structs/tuples aren't touching
ByteAtATime May 31, 2024
f5e3861
fix: change ArrayDisplay to show indexes in brackets
ByteAtATime May 31, 2024
dbea593
fix: change "format as ether/number" tooltip to "divide/multiply by 1…
ByteAtATime May 31, 2024
2183474
fix: transaction receipt is not a struct
ByteAtATime Jun 1, 2024
35adf37
fix: rename tuple to array
ByteAtATime Jun 1, 2024
6d54d67
refactor: change default tx display size to "base"
ByteAtATime Jun 1, 2024
a89b901
feat: show "[]" for empty array
ByteAtATime Jun 1, 2024
c59c443
refactor: rename array and struct parameter to values/struct
ByteAtATime Jun 1, 2024
19ef4c7
fix: use ternary instead of && for conditional class
ByteAtATime Jun 1, 2024
2aafc6b
fix responsiveness on mobile for ReadOnlyFunctionForm
technophile-04 Jun 10, 2024
576886a
use btn-ghost for Number action btn
technophile-04 Jun 10, 2024
25bd2c1
change IntegerInput tooltip to 1e18
technophile-04 Jun 10, 2024
e2d8a71
decrease the of ArrowIcons to 65
technophile-04 Jun 10, 2024
6d168a2
align key values and avoid wrap on small screens
technophile-04 Jun 10, 2024
f801260
Merge branch 'main' into better-tx-result-formatting
technophile-04 Jun 11, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,11 @@ export const ReadOnlyFunctionForm = ({
</p>
{inputElements}
<div className="flex justify-between gap-2 flex-wrap">
<div className="flex-grow w-4/5">
<div className="flex-grow max-w-[80%]">
technophile-04 marked this conversation as resolved.
Show resolved Hide resolved
{result !== null && result !== undefined && (
<div className="bg-secondary rounded-3xl text-sm px-4 py-1.5 break-words">
<div className="bg-secondary rounded-3xl text-sm px-4 py-1.5 break-words overflow-auto">
<p className="font-bold m-0 mb-1">Result:</p>
<pre className="whitespace-pre-wrap break-words">{displayTxResult(result)}</pre>
<pre className="whitespace-pre-wrap break-words">{displayTxResult(result, "sm")}</pre>
</div>
)}
</div>
Expand Down
17 changes: 10 additions & 7 deletions packages/nextjs/app/debug/_components/contract/TxReceipt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ import { useState } from "react";
import { CopyToClipboard } from "react-copy-to-clipboard";
import { TransactionReceipt } from "viem";
import { CheckCircleIcon, DocumentDuplicateIcon } from "@heroicons/react/24/outline";
import { displayTxResult } from "~~/app/debug/_components/contract";
import { ObjectFieldDisplay } from "~~/app/debug/_components/contract";
import { replacer } from "~~/utils/scaffold-eth/common";

export const TxReceipt = (
txResult: string | number | bigint | Record<string, any> | TransactionReceipt | undefined,
) => {
export const TxReceipt = ({ txResult }: { txResult: TransactionReceipt }) => {
const [txResultCopied, setTxResultCopied] = useState(false);

return (
Expand All @@ -19,7 +18,7 @@ export const TxReceipt = (
/>
) : (
<CopyToClipboard
text={displayTxResult(txResult) as string}
text={JSON.stringify(txResult, replacer, 2)}
onCopy={() => {
setTxResultCopied(true);
setTimeout(() => {
Expand All @@ -39,8 +38,12 @@ export const TxReceipt = (
<div className="collapse-title text-sm min-h-0 py-1.5 pl-1">
<strong>Transaction Receipt</strong>
</div>
<div className="collapse-content overflow-auto bg-secondary rounded-t-none rounded-3xl">
<pre className="text-xs pt-4">{displayTxResult(txResult)}</pre>
<div className="collapse-content overflow-auto bg-secondary rounded-t-none rounded-3xl !pl-0">
<pre className="text-xs">
{Object.entries(txResult).map(([k, v]) => (
<ObjectFieldDisplay name={k} value={v} size="xs" leftPad={false} key={k} />
))}
</pre>
</div>
</div>
</div>
Expand Down
108 changes: 83 additions & 25 deletions packages/nextjs/app/debug/_components/contract/utilsDisplay.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ReactElement } from "react";
import { TransactionBase, TransactionReceipt, formatEther, isAddress } from "viem";
import { ReactElement, useState } from "react";
import { TransactionBase, TransactionReceipt, formatEther, isAddress, isHex } from "viem";
import { ArrowsRightLeftIcon } from "@heroicons/react/24/solid";
import { Address } from "~~/components/scaffold-eth";
import { replacer } from "~~/utils/scaffold-eth/common";

Expand All @@ -13,44 +14,101 @@ type DisplayContent =
| undefined
| unknown;

type ResultFontSize = "sm" | "base" | "xs" | "lg" | "xl" | "2xl" | "3xl";

export const displayTxResult = (
displayContent: DisplayContent | DisplayContent[],
asText = false,
fontSize: ResultFontSize = "base",
): string | ReactElement | number => {
if (displayContent == null) {
return "";
}

if (typeof displayContent === "bigint") {
try {
const asNumber = Number(displayContent);
if (asNumber <= Number.MAX_SAFE_INTEGER && asNumber >= Number.MIN_SAFE_INTEGER) {
return asNumber;
} else {
return "Ξ" + formatEther(displayContent);
}
} catch (e) {
return "Ξ" + formatEther(displayContent);
}
return <NumberDisplay value={displayContent} />;
}

if (typeof displayContent === "string" && isAddress(displayContent)) {
return asText ? displayContent : <Address address={displayContent} />;
if (typeof displayContent === "string") {
if (isAddress(displayContent)) {
return <Address address={displayContent} size={fontSize} />;
}

if (isHex(displayContent)) {
return displayContent; // don't add quotes
}
ByteAtATime marked this conversation as resolved.
Show resolved Hide resolved
}

if (Array.isArray(displayContent)) {
const mostReadable = (v: DisplayContent) =>
["number", "boolean"].includes(typeof v) ? v : displayTxResultAsText(v);
const displayable = JSON.stringify(displayContent.map(mostReadable), replacer);

return asText ? (
displayable
) : (
<span style={{ overflowWrap: "break-word", width: "100%" }}>{displayable.replaceAll(",", ",\n")}</span>
);
return <ArrayDisplay values={displayContent} size={fontSize} />;
}

if (typeof displayContent === "object") {
return <StructDisplay struct={displayContent} size={fontSize} />;
technophile-04 marked this conversation as resolved.
Show resolved Hide resolved
ByteAtATime marked this conversation as resolved.
Show resolved Hide resolved
}

return JSON.stringify(displayContent, replacer, 2);
};

const displayTxResultAsText = (displayContent: DisplayContent) => displayTxResult(displayContent, true);
const NumberDisplay = ({ value }: { value: bigint }) => {
const [isEther, setIsEther] = useState(false);

const asNumber = Number(value);
if (asNumber <= Number.MAX_SAFE_INTEGER && asNumber >= Number.MIN_SAFE_INTEGER) {
return String(value);
ByteAtATime marked this conversation as resolved.
Show resolved Hide resolved
}

return (
<span>
{isEther ? "Ξ" + formatEther(value) : String(value)}
<span
rin-st marked this conversation as resolved.
Show resolved Hide resolved
className="tooltip tooltip-secondary font-sans ml-2"
data-tip={isEther ? "Multiply by 1e18" : "Divide by 1e18"}
>
technophile-04 marked this conversation as resolved.
Show resolved Hide resolved
<button className="btn btn-primary btn-circle btn-xs" onClick={() => setIsEther(!isEther)}>
<ArrowsRightLeftIcon className="h-3 w-3" />
</button>
ByteAtATime marked this conversation as resolved.
Show resolved Hide resolved
</span>
</span>
);
};

export const ObjectFieldDisplay = ({
name,
value,
size,
leftPad = true,
}: {
name: string;
value: DisplayContent;
size: ResultFontSize;
leftPad?: boolean;
}) => {
return (
<div className={`flex flex-row ${leftPad ? "ml-4" : ""}`}>
<span className="text-gray-500 dark:text-gray-400 mr-2">{name}:</span>
<span className="text-base-content">{displayTxResult(value, size)}</span>
</div>
technophile-04 marked this conversation as resolved.
Show resolved Hide resolved
);
};

const ArrayDisplay = ({ values, size }: { values: DisplayContent[]; size: ResultFontSize }) => {
return (
<div className="flex flex-col gap-y-1">
{values.length ? "array" : "[]"}
{values.map((v, i) => (
<ObjectFieldDisplay key={i} name={`[${i}]`} value={v} size={size} />
))}
</div>
);
};

const StructDisplay = ({ struct, size }: { struct: Record<string, any>; size: ResultFontSize }) => {
return (
<div className="flex flex-col gap-y-1">
struct
{Object.entries(struct).map(([k, v]) => (
<ObjectFieldDisplay key={k} name={k} value={v} size={size} />
))}
</div>
);
};
Loading