Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4,708 changes: 4,669 additions & 39 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"react-scripts": "5.0.1",
"react-simple-code-editor": "^0.13.1",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
"web-vitals": "^2.1.4",
"web3": "^1.9.0"
},
"scripts": {
"start": "react-scripts start",
Expand Down
49 changes: 43 additions & 6 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,58 @@ import './App.css'
import 'prism-themes/themes/prism-darcula.css'
import { Stack } from '@mui/material'
import Panel from './components/panel'
import Console from './components/console'
import AutoDismissAlert from './components/autoDismissAlert'
import { Addresses } from './components/addresses'
import { CodeEditor } from './components/editor'
import { Tabs, Tab, Typography, Box } from '@mui/material'

interface TabPanelProps {
children?: React.ReactNode
index: number
value: number
}

function TabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props

return (
<div
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}>
{value === index && (
<Box sx={{ p: 3 }}>
<Typography>{children}</Typography>
</Box>
)}
</div>
)
}

function App() {
const [value, setValue] = React.useState(0)

const handleChange = (event: React.SyntheticEvent, newValue: number) => {
setValue(newValue)
}
return (
<Stack direction="column" justifyContent="space-between" alignItems="center" spacing={0}>
<Stack direction="row" justifyContent="center" alignItems="center" spacing={0}>
<Stack direction="column" justifyContent="space-between" alignItems="center" spacing={0}>
<Addresses />
<Panel />
<Tabs value={value} onChange={handleChange} sx={{my: 6}}>
<Tab label="Addresses" />
<Tab label="Panel" />
</Tabs>
<TabPanel value={value} index={0}>
<Addresses />
</TabPanel>
<TabPanel value={value} index={1}>
<Panel />
</TabPanel>
</Stack>
<CodeEditor />
</Stack>
<Console />
<AutoDismissAlert />
</Stack>
)
}
Expand Down
35 changes: 27 additions & 8 deletions src/atoms.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,34 @@
import { atom } from 'jotai'
import { loadState } from './util'

export const addressesAtom = atom<string[]>([
"0x000000000000000000000000000000000cafe111",
"0x000000000000000000000000000000000cafe222"
])
export const activeAddressAtom = atom<string | null>(null)
export const addressCodeMapAtom = atom<Map<string, string>>(new Map())
const initialAccounts = [
{
address: '0x000000000000000000000000000000000cafe111',
code: '',
storage: [{ key: '', value: '' }]
},
{
address: '0x000000000000000000000000000000000cafe222',
code: '',
storage: [{ key: '', value: '' }]
}
]

interface Log {
level: 'debug' | 'info' | 'warn' | 'error'
export interface Account {
address: string,
nonce?: string,
balance?: string,
code: string,
storage: any,
}

console.log(loadState('ACCOUNT_ATOM') || initialAccounts)

export const accountsAtom = atom<Account[]>(loadState('ACCOUNT_ATOM') as Account[] || initialAccounts)
export interface Log {
level: 'success' | 'info' | 'warning' | 'error'
message: string
id: number
}

export const logsAtom = atom<Array<Log>>([])
16 changes: 7 additions & 9 deletions src/components/address.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
import { ListItem, ListItemButton, ListItemText } from '@mui/material'
import EditIcon from '@mui/icons-material/Edit'
import FileOpenIcon from '@mui/icons-material/FileOpen'
import DeleteIcon from '@mui/icons-material/Delete'
import React from 'react'
import { AddressDialog } from './addressDialog'
import { activeAddressAtom } from '../atoms'
import { useAtom } from 'jotai'
import { Account } from '../atoms'

interface AddressItemProps {
index: number
address: string
onEdit: (_: string) => void
onEdit: (_: Account) => void
onDelete: () => void
}

export const AddressItem = ({ index, address, onDelete, onEdit }: AddressItemProps) => {
const [, setActiveAddressAtom] = useAtom(activeAddressAtom)
const [dialogOpen, setDialogOpen] = React.useState(false)

return (
Expand All @@ -30,10 +27,11 @@ export const AddressItem = ({ index, address, onDelete, onEdit }: AddressItemPro
}}
/>
<ListItemButton>
<EditIcon onClick={() => setDialogOpen(true)} />
</ListItemButton>
<ListItemButton>
<FileOpenIcon onClick={() => setActiveAddressAtom(address)} />
<EditIcon
onClick={() => {
setDialogOpen(true)
}}
/>
</ListItemButton>
<ListItemButton>
<DeleteIcon onClick={onDelete} />
Expand Down
179 changes: 152 additions & 27 deletions src/components/addressDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,33 @@
import { Button, Dialog, DialogTitle, IconButton, TextField } from '@mui/material'
import { Button, Dialog, DialogTitle, IconButton, TextField, Grid } from '@mui/material'
import CloseIcon from '@mui/icons-material/Close'
import React from 'react'
import React, { useState, ChangeEvent, useEffect } from 'react'
import { Box } from '@mui/system'
import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline'
import RemoveCircleOutlineIcon from '@mui/icons-material/RemoveCircleOutline'
import { CodeEditor } from '../components/editor'
import { Account } from '../atoms'

import { useAtom } from 'jotai'
import { accountsAtom } from '../atoms'

interface AddressDialogProps {
open: boolean
initialValue: string
onSave: (_: string) => void
onSave: (_: Account) => void
clearOnSave?: boolean
onClose: () => void
}

interface InputField {
key: string
value: string
}
export interface DialogTitleProps {
onClose: () => void
}

const AddressDialogTitle = (props: DialogTitleProps) => {
const { onClose } = props

return (
<DialogTitle sx={{ m: 0, p: 2 }}>
Address
Expand All @@ -36,31 +47,145 @@ const AddressDialogTitle = (props: DialogTitleProps) => {

export const AddressDialog = ({ open, initialValue, onSave, clearOnSave, onClose }: AddressDialogProps) => {
const [dialogValue, setDialogValue] = React.useState(initialValue)
const [inputFields, setInputFields] = useState<InputField[]>([{ key: '', value: '' }])
const [balance, setBalance] = useState('')
const [nonce, setNonce] = useState('0')
const [code, setCode] = useState('')
const [accounts] = useAtom(accountsAtom)

useEffect(() => {
if (initialValue) {
const account = accounts.find((account) => account.address === initialValue)
setInputFields(account?.storage)
setBalance(account?.balance || '')
setNonce(account?.nonce || '')
setCode(account?.code || '')
}
}, [initialValue])

const handleAddFields = () => {
setInputFields([...inputFields, { key: '', value: '' }])
}

const handleRemoveFields = (index: number) => {
const newInputFields = [...inputFields]
newInputFields.splice(index, 1)
setInputFields(newInputFields)
}

const handleChange = (index: number, event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const newInputFields = [...inputFields]
//@ts-ignore
newInputFields[index][event.target.name] = event.target.value
setInputFields(newInputFields)
}

const updateCode = (code: string) => {
setCode(code)
}

const handleSave = () => {
const account: Account = {
address: dialogValue,
code,
storage: inputFields,
balance,
nonce,
}
onSave(account)
}

return (
<Dialog open={open} onClose={onClose} fullWidth={true} maxWidth="sm">
<AddressDialogTitle onClose={onClose} />
<TextField
autoFocus
margin="normal"
label="Address"
variant="outlined"
value={dialogValue}
onChange={(e) => setDialogValue(e.target.value)}
inputProps={{ sx: { fontFamily: '"Fira code", "Fira Mono", monospace' } }}
/>
<Button
variant="contained"
color="primary"
onClick={() => {
onSave(dialogValue)
onClose()
if (clearOnSave) {
setDialogValue('')
}
}}>
Save
</Button>
<Dialog open={open} onClose={onClose} fullWidth={true} maxWidth="md">
<Box sx={{ p: 2 }} display="flex" flexDirection="column">
<AddressDialogTitle onClose={onClose} />
<Grid container spacing={2} style={{ maxWidth: '1000px' }}>
<Grid item xs={12}>
<TextField
autoFocus
fullWidth
margin="normal"
label="Address"
variant="outlined"
value={dialogValue}
onChange={(e) => setDialogValue(e.target.value)}
inputProps={{ sx: { fontFamily: '"Fira code", "Fira Mono", monospace' } }}
/>
</Grid>

<Grid item xs={6}>
<TextField
label="Balance"
value={balance}
onChange={(e) => setBalance(e.target.value)}
fullWidth
InputProps={{ sx: { fontFamily: '"Fira code", "Fira Mono", monospace' } }}
/>
</Grid>
<Grid item xs={6}>
<TextField
label="Nonce"
value={nonce}
onChange={(e) => setNonce(e.target.value)}
fullWidth
InputProps={{ sx: { fontFamily: '"Fira code", "Fira Mono", monospace' } }}
/>
</Grid>

{inputFields.map((inputField, index) => (
<>
<Grid item xs={6}>
<TextField
name="key"
label="Storage Key"
fullWidth
value={inputField.key}
onChange={(event) => handleChange(index, event)}
/>
</Grid>
<Grid item xs={4}>
<TextField
name="value"
label="Storage Value"
fullWidth
value={inputField.value}
onChange={(event) => handleChange(index, event)}
/>
</Grid>
<Grid item xs={1}>
<IconButton onClick={handleAddFields} color="primary">
<AddCircleOutlineIcon />
</IconButton>
</Grid>
<Grid item xs={1}>
{inputFields.length > 1 && (
<IconButton onClick={() => handleRemoveFields(index)} color="secondary">
<RemoveCircleOutlineIcon />
</IconButton>
)}
</Grid>
</>
))}

<Grid item xs={12}>
<CodeEditor address={initialValue} onSave={updateCode} initialValue={code} />
</Grid>
</Grid>

<Button
variant="contained"
color="primary"
sx={{ mt: 4 }}
onClick={() => {
handleSave()
onClose()
if (clearOnSave) {
setDialogValue('')
}
}}>
Save
</Button>
</Box>
</Dialog>
)
}
Loading