Skip to content

Commit

Permalink
Merge pull request #102 from FuelLabs/sophie/abi-view
Browse files Browse the repository at this point in the history
feat: Add ABI view
  • Loading branch information
sdankel authored Dec 24, 2024
2 parents c14141e + 5ab77db commit 56ad98b
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 1 deletion.
79 changes: 79 additions & 0 deletions app/src/AbiApp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { useCallback, useEffect, useState } from "react";
import LogView from "./features/editor/components/LogView";
import { loadAbi, saveAbi, saveSwayCode } from "./utils/localStorage";
import InteractionDrawer from "./features/interact/components/InteractionDrawer";
import { useLog } from "./features/editor/hooks/useLog";
import { Analytics } from "@vercel/analytics/react";
import useTheme from "./context/theme";
import AbiActionToolbar from "./features/toolbar/components/AbiActionToolbar";
import AbiEditorView from "./features/editor/components/AbiEditorView";

const DRAWER_WIDTH = "40vw";

function AbiApp() {
// The current sway code in the editor.
const [abiCode, setAbiCode] = useState<string>(loadAbi());

// Functions for reading and writing to the log output.
const [log, updateLog] = useLog();

// The contract ID of the deployed contract.
const [contractId, setContractId] = useState("");

// An error message to display to the user.
const [drawerOpen, setDrawerOpen] = useState(false);

// The theme color for the app.
const { themeColor } = useTheme();

// Update the ABI in localstorage when the editor changes.
useEffect(() => {
saveAbi(abiCode);
}, [abiCode]);

const onSwayCodeChange = useCallback(
(code: string) => {
saveSwayCode(code);
setAbiCode(code);
},
[setAbiCode],
);

return (
<div
style={{
padding: "15px",
margin: "0px",
background: themeColor("white4"),
}}
>
<AbiActionToolbar drawerOpen={drawerOpen} setDrawerOpen={setDrawerOpen} />
<div
style={{
marginRight: drawerOpen ? DRAWER_WIDTH : 0,
transition: "margin 195ms cubic-bezier(0.4, 0, 0.6, 1) 0ms",
height: "calc(100vh - 95px)",
display: "flex",
flexDirection: "column",
}}
>
<AbiEditorView
abiCode={abiCode}
onAbiCodeChange={onSwayCodeChange}
contractId={contractId}
setContractId={setContractId}
/>
<LogView results={log} />
</div>
<InteractionDrawer
isOpen={drawerOpen}
width={DRAWER_WIDTH}
contractId={contractId}
updateLog={updateLog}
/>
<Analytics />
</div>
);
}

export default AbiApp;
50 changes: 50 additions & 0 deletions app/src/features/editor/components/AbiEditorView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from "react";
import { useIsMobile } from "../../../hooks/useIsMobile";
import JsonEditor from "./JsonEditor";
import { TextField } from "@mui/material";

export interface AbiEditorViewProps {
abiCode: string;
onAbiCodeChange: (value: string) => void;
contractId: string;
setContractId: (contractId: string) => void;
}

function AbiEditorView({
abiCode,
onAbiCodeChange,
contractId,
setContractId,
}: AbiEditorViewProps) {
const isMobile = useIsMobile();

return (
<div>
<TextField
id="contract-id"
label="Contract ID"
variant="outlined"
size="small"
value={contractId}
onChange={(e) => setContractId(e.target.value)}
style={{ width: "100%", marginBottom: "10px" }}
/>
<div
style={{
display: "flex",
flexDirection: isMobile ? "column" : "row",
height: "50vh",
minHeight: "10vh",
maxHeight: "80vh",
position: "relative",
resize: isMobile ? "none" : "vertical",
overflow: "auto",
}}
>
<JsonEditor code={abiCode} onChange={onAbiCodeChange} />
</div>
</div>
);
}

export default AbiEditorView;
37 changes: 37 additions & 0 deletions app/src/features/editor/components/JsonEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from "react";
import AceEditor from "react-ace";
import "ace-builds/webpack-resolver";
import "ace-builds/src-noconflict/mode-json";
import "ace-builds/src-noconflict/theme-chrome";
import "ace-builds/src-noconflict/theme-tomorrow_night_bright";
import { StyledBorder } from "../../../components/shared";
import useTheme from "../../../context/theme";

export interface JsonEditorProps {
code: string;
onChange: (value: string) => void;
}

function JsonEditor({ code, onChange }: JsonEditorProps) {
const { editorTheme, themeColor } = useTheme();

return (
<StyledBorder style={{ flex: 1 }} themeColor={themeColor}>
<AceEditor
style={{
width: "100%",
height: "100%",
}}
mode="json"
theme={editorTheme}
name="editor"
fontSize="14px"
onChange={onChange}
value={code}
editorProps={{ $blockScrolling: true }}
/>
</StyledBorder>
);
}

export default JsonEditor;
59 changes: 59 additions & 0 deletions app/src/features/toolbar/components/AbiActionToolbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React, { useCallback } from "react";
import OpenInNew from "@mui/icons-material/OpenInNew";
import SecondaryButton from "../../../components/SecondaryButton";
import { useIsMobile } from "../../../hooks/useIsMobile";
import SwitchThemeButton from "./SwitchThemeButton";
import { useConnectIfNotAlready } from "../hooks/useConnectIfNotAlready";
import { useDisconnect } from "@fuels/react";

export interface AbiActionToolbarProps {
drawerOpen: boolean;
setDrawerOpen: (open: boolean) => void;
}

function AbiActionToolbar({
drawerOpen,
setDrawerOpen,
}: AbiActionToolbarProps) {
const isMobile = useIsMobile();
const { isConnected, connect } = useConnectIfNotAlready();
const { disconnect } = useDisconnect();

const onDocsClick = useCallback(() => {
window.open("https://docs.fuel.network/docs/sway", "_blank", "noreferrer");
}, []);

return (
<div
style={{
margin: "5px 0 10px",
display: isMobile ? "inline-table" : "flex",
}}
>
<SecondaryButton
header={true}
onClick={() => setDrawerOpen(!drawerOpen)}
text="INTERACT"
tooltip="Interact with the contract ABI"
/>
<SecondaryButton
header={true}
onClick={onDocsClick}
text="DOCS"
tooltip={"Open documentation for Sway in a new tab"}
endIcon={<OpenInNew style={{ fontSize: "16px" }} />}
/>
<SecondaryButton
header={true}
onClick={isConnected ? disconnect : connect}
text={isConnected ? "DISCONNECT" : "CONNECT"}
tooltip={
isConnected ? "Disconnect from the wallet" : "Connect to a wallet"
}
/>
{!isMobile && <SwitchThemeButton />}
</div>
);
}

export default AbiActionToolbar;
8 changes: 8 additions & 0 deletions app/src/features/toolbar/components/ActionToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useIsMobile } from "../../../hooks/useIsMobile";
import SwitchThemeButton from "./SwitchThemeButton";
import { useConnectIfNotAlready } from "../hooks/useConnectIfNotAlready";
import { useDisconnect } from "@fuels/react";
import { useNavigate } from "react-router-dom";

export interface ActionToolbarProps {
deployState: DeployState;
Expand Down Expand Up @@ -45,6 +46,7 @@ function ActionToolbar({
const isMobile = useIsMobile();
const { isConnected } = useConnectIfNotAlready();
const { disconnect } = useDisconnect();
const navigate = useNavigate();

const onDocsClick = useCallback(() => {
window.open("https://docs.fuel.network/docs/sway", "_blank", "noreferrer");
Expand Down Expand Up @@ -99,6 +101,12 @@ function ActionToolbar({
: "Show the Solidity editor to transpile Solidity to Sway"
}
/>
<SecondaryButton
header={true}
onClick={() => navigate("/abi")}
text="ABI"
tooltip="Query an already-deployed contract using the ABI"
/>
<SecondaryButton
header={true}
onClick={onDocsClick}
Expand Down
2 changes: 1 addition & 1 deletion app/src/features/toolbar/hooks/useConnectIfNotAlready.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,5 @@ export function useConnectIfNotAlready() {
}
}, [isError, failedCallbackRef]);

return { connectIfNotAlready, isConnected };
return { connectIfNotAlready, isConnected, connect };
}
5 changes: 5 additions & 0 deletions app/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import AbiApp from "./AbiApp";
import reportWebVitals from "./reportWebVitals";
import "./index.css";
import { Providers } from "./components/Providers";
Expand All @@ -11,6 +12,10 @@ const router = createBrowserRouter([
path: "/",
element: <App />,
},
{
path: "/abi",
element: <AbiApp />,
},
]);

const root = ReactDOM.createRoot(
Expand Down

0 comments on commit 56ad98b

Please sign in to comment.