From 7a5b9c8d1d1b725d497782f52e5615f672747b78 Mon Sep 17 00:00:00 2001 From: Noah Gundotra Date: Wed, 9 Oct 2024 13:39:23 -0400 Subject: [PATCH] IDL Fixes (#384) * fix long byte array display for anchor ix arguments * add option to download idl * add badge to show IDL version * add toggle for viewing expanded IDL (no more clicking manually to expand) --- app/components/account/AnchorProgramCard.tsx | 61 +++++++++++++++---- .../UpgradeableLoaderAccountSection.tsx | 6 +- app/components/common/Downloadable.tsx | 44 ++++++++++++- app/components/common/IDLBadge.tsx | 14 +++++ app/types/react-json-view.d.ts | 7 +++ app/utils/anchor.tsx | 27 +++++++- app/utils/convertLegacyIdl.ts | 26 ++++---- 7 files changed, 156 insertions(+), 29 deletions(-) create mode 100644 app/components/common/IDLBadge.tsx create mode 100644 app/types/react-json-view.d.ts diff --git a/app/components/account/AnchorProgramCard.tsx b/app/components/account/AnchorProgramCard.tsx index d8bdd24e..26422295 100644 --- a/app/components/account/AnchorProgramCard.tsx +++ b/app/components/account/AnchorProgramCard.tsx @@ -2,31 +2,70 @@ import { useAnchorProgram } from '@providers/anchor'; import { useCluster } from '@providers/cluster'; +import { useState } from 'react'; import ReactJson from 'react-json-view'; +import { getIdlSpecType } from '@/app/utils/convertLegacyIdl'; + +import { DownloadableButton } from '../common/Downloadable'; +import { IDLBadge } from '../common/IDLBadge'; + export function AnchorProgramCard({ programId }: { programId: string }) { const { url } = useCluster(); const { idl } = useAnchorProgram(programId, url); + const [collapsedValue, setCollapsedValue] = useState(1); if (!idl) { return null; } + const spec = getIdlSpecType(idl); return ( - <> -
-
-
-
-

Anchor IDL

-
+
+
+
+
+

Anchor IDL

+
+
+ + Download IDL +
- -
- +
+
+ +
+ setCollapsedValue(e.target.checked ? false : 1)} + /> +
- + +
+ +
+
); } diff --git a/app/components/account/UpgradeableLoaderAccountSection.tsx b/app/components/account/UpgradeableLoaderAccountSection.tsx index 5c4b2af5..c14a4bdd 100644 --- a/app/components/account/UpgradeableLoaderAccountSection.tsx +++ b/app/components/account/UpgradeableLoaderAccountSection.tsx @@ -1,6 +1,6 @@ import { UnknownAccountCard } from '@components/account/UnknownAccountCard'; import { Address } from '@components/common/Address'; -import { Downloadable } from '@components/common/Downloadable'; +import { DownloadableIcon } from '@components/common/Downloadable'; import { InfoTooltip } from '@components/common/InfoTooltip'; import { SecurityTXTBadge } from '@components/common/SecurityTXTBadge'; import { Slot } from '@components/common/Slot'; @@ -233,9 +233,9 @@ export function UpgradeableProgramDataSection({ Data Size (Bytes) - + {account.space} - + )} diff --git a/app/components/common/Downloadable.tsx b/app/components/common/Downloadable.tsx index 46836f9c..67b15da0 100644 --- a/app/components/common/Downloadable.tsx +++ b/app/components/common/Downloadable.tsx @@ -1,7 +1,15 @@ -import { ReactNode } from 'react'; -import { Download } from 'react-feather'; +import { ComponentType, ReactNode } from 'react'; +import { Download, IconProps } from 'react-feather'; -export function Downloadable({ data, filename, children }: { data: string; filename: string; children: ReactNode }) { +export function DownloadableIcon({ + data, + filename, + children, +}: { + data: string; + filename: string; + children: ReactNode; +}) { const handleClick = async () => { const blob = new Blob([Buffer.from(data, 'base64')]); const fileDownloadUrl = URL.createObjectURL(blob); @@ -18,3 +26,33 @@ export function Downloadable({ data, filename, children }: { data: string; filen ); } + +export function DownloadableButton({ + data, + filename, + children, + type, + icon: Icon = Download as ComponentType, +}: { + data: string; + filename: string; + children?: ReactNode; + type?: string; + icon?: ComponentType; +}) { + const handleDownload = async () => { + const blob = new Blob([Buffer.from(data, 'base64')], type ? { type } : {}); + const fileDownloadUrl = URL.createObjectURL(blob); + const tempLink = document.createElement('a'); + tempLink.href = fileDownloadUrl; + tempLink.setAttribute('download', filename); + tempLink.click(); + }; + + return ( +
+ + {children} +
+ ); +} diff --git a/app/components/common/IDLBadge.tsx b/app/components/common/IDLBadge.tsx new file mode 100644 index 00000000..cdb847be --- /dev/null +++ b/app/components/common/IDLBadge.tsx @@ -0,0 +1,14 @@ +import React from 'react'; + +import { IdlSpec } from '@/app/utils/convertLegacyIdl'; + +interface IDLBadgeProps { + spec: IdlSpec; +} + +export function IDLBadge({ spec }: IDLBadgeProps) { + const badgeClass = spec === 'legacy' ? 'bg-warning' : 'bg-success'; + const badgeText = spec === 'legacy' ? 'Legacy' : '0.30.1'; + + return {badgeText} Anchor IDL; +} diff --git a/app/types/react-json-view.d.ts b/app/types/react-json-view.d.ts new file mode 100644 index 00000000..20800da1 --- /dev/null +++ b/app/types/react-json-view.d.ts @@ -0,0 +1,7 @@ +import 'react-json-view'; + +declare module 'react-json-view' { + interface ReactJsonViewProps { + displayArrayKey?: boolean; + } +} diff --git a/app/utils/anchor.tsx b/app/utils/anchor.tsx index 00bccd85..e305793a 100644 --- a/app/utils/anchor.tsx +++ b/app/utils/anchor.tsx @@ -200,7 +200,7 @@ function mapField(key: string, value: any, type: IdlType, idl: Idl, keySuffix?:
{numberWithSeparator(value.toString())}
); - } else if (type === 'bool' || type === 'bytes' || type === 'string') { + } else if (type === 'bool' || type === 'string') { return ( {value.toString()}
); + } else if (type === 'bytes') { + return ( + +
+ {(value as Buffer).toString('base64')} +
+
+ ); } else if (type === 'pubkey') { return (