Skip to content

Commit

Permalink
feat: add more cmds to cast tool ui, keep cast state
Browse files Browse the repository at this point in the history
  • Loading branch information
daoleno committed Sep 25, 2024
1 parent cfcdb31 commit 72ce90a
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 49 deletions.
Binary file modified bun.lockb
Binary file not shown.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
"dependencies": {
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-sql": "^6.7.1",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-collapsible": "^1.1.0",
Expand Down
219 changes: 173 additions & 46 deletions src/components/CastTool.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
import { invoke } from '@tauri-apps/api/tauri'
import { HelpCircle, PlayIcon, XIcon } from 'lucide-react'
import type React from 'react'
import { useState } from 'react'
import { useCastToolStore } from '../stores/castToolStore'
import RPCInput from './RPCInput'
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from './ui/accordion'
import { Button } from './ui/button'
import { Input } from './ui/input'
import { Label } from './ui/label'
import { ScrollArea } from './ui/scroll-area'
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip'

interface CastCommandProps {
id: number
name: string
description: string
args: string[]
onRemove: () => void
onRemove: (id: number) => void
}

const CastCommand: React.FC<CastCommandProps> = ({
id,
name,
description,
args,
Expand All @@ -24,9 +35,11 @@ const CastCommand: React.FC<CastCommandProps> = ({
const [output, setOutput] = useState<string>('')

const handleInputChange = (index: number, value: string) => {
const newInputs = [...inputs]
newInputs[index] = value
setInputs(newInputs)
setInputs((prev) => {
const newInputs = [...prev]
newInputs[index] = value
return newInputs
})
}

const runCommand = async () => {
Expand Down Expand Up @@ -61,23 +74,36 @@ const CastCommand: React.FC<CastCommandProps> = ({
<Button variant="ghost" size="icon" onClick={runCommand}>
<PlayIcon className="h-4 w-4" />
</Button>
<Button variant="ghost" size="icon" onClick={onRemove}>
<Button variant="ghost" size="icon" onClick={() => onRemove(id)}>
<XIcon className="h-4 w-4" />
</Button>
</div>
</div>
<div className="grid grid-cols-2 gap-1">
{args.map((arg, index) => (
<div key={`${name}-${arg}-${index}`}>
<Label htmlFor={`${name}-${arg}-${index}`} className="text-xs">
<div key={`${id}-${arg}-${index}`}>
<Label htmlFor={`${id}-${arg}-${index}`} className="text-xs">
{arg}
</Label>
<Input
id={`${name}-${arg}-${index}`}
value={inputs[index]}
onChange={(e) => handleInputChange(index, e.target.value)}
className="h-6 text-xs"
/>
{arg.toLowerCase() === 'rpc' ? (
<RPCInput
value={inputs[index]}
onChange={(value) => handleInputChange(index, value)}
placeholder={`Enter ${arg}`}
className="h-6 text-xs"
/>
) : (
<Input
id={`${id}-${arg}-${index}`}
value={inputs[index]}
onChange={(e) => handleInputChange(index, e.target.value)}
className="h-6 text-xs"
autoComplete="off"
autoCapitalize="off"
autoCorrect="off"
spellCheck="false"
/>
)}
</div>
))}
</div>
Expand All @@ -94,11 +120,14 @@ const CastCommand: React.FC<CastCommandProps> = ({
}

const CastTool: React.FC = () => {
const [activeCommands, setActiveCommands] = useState<
Array<{ name: string; id: number }>
>([])
const [nextId, setNextId] = useState(0)
const [searchTerm, setSearchTerm] = useState('')
const {
activeCommands,
searchTerm,
addCommand,
removeCommand,
clearAllCommands,
setSearchTerm,
} = useCastToolStore()

const castCommands = [
{
Expand Down Expand Up @@ -434,22 +463,92 @@ const CastTool: React.FC = () => {
},
]

const filteredCommands = castCommands.filter((cmd) =>
cmd.name.toLowerCase().includes(searchTerm.toLowerCase()),
)

const addCommand = (name: string) => {
setActiveCommands([...activeCommands, { name, id: nextId }])
setNextId(nextId + 1)
}

const removeCommand = (id: number) => {
setActiveCommands(activeCommands.filter((cmd) => cmd.id !== id))
}

const clearAllCommands = () => {
setActiveCommands([])
}
const commandCategories = [
{
name: 'Blockchain & RPC Queries',
commands: [
'age',
'balance',
'base-fee',
'block',
'block-number',
'chain',
'chain-id',
'client',
'code',
'codesize',
'compute-address',
'gas-price',
'implementation',
'admin',
'nonce',
'storage',
'proof',
'receipt',
],
},
{
name: 'Constants & Conversions',
commands: [
'max-int',
'min-int',
'max-uint',
'address-zero',
'hash-zero',
'from-utf8',
'to-ascii',
'to-utf8',
'from-fixed-point',
'to-fixed-point',
'concat-hex',
'from-bin',
'to-hex-data',
'to-checksum-address',
'to-uint256',
'to-int256',
'to-unit',
'from-wei',
'to-wei',
'from-rlp',
'to-rlp',
'to-hex',
'to-dec',
'to-base',
'to-bytes32',
'format-bytes32-string',
'parse-bytes32-string',
'parse-bytes32-address',
],
},
{
name: 'ABI Encoding & Decoding',
commands: [
'abi-decode',
'abi-encode',
'calldata-decode',
'calldata-encode',
],
},
{
name: 'ENS',
commands: ['namehash', 'lookup-address', 'resolve-name'],
},
{
name: 'Misc',
commands: [
'keccak',
'hash-message',
'sig-event',
'left-shift',
'right-shift',
'disassemble',
'index',
'index-erc7201',
'decode-transaction',
'decode-eof',
],
},
]

return (
<div className="flex h-full">
Expand All @@ -461,16 +560,43 @@ const CastTool: React.FC = () => {
className="mb-4"
/>
<ScrollArea className="h-[calc(100vh-8rem)]">
{filteredCommands.map((cmd) => (
<Button
key={cmd.name}
onClick={() => addCommand(cmd.name)}
className="w-full justify-start mb-2 text-left"
variant="ghost"
>
{cmd.name}
</Button>
))}
<Accordion type="multiple" className="w-full">
{commandCategories.map((category) => (
<AccordionItem value={category.name} key={category.name}>
<AccordionTrigger>{category.name}</AccordionTrigger>
<AccordionContent>
{category.commands
.filter((cmd) =>
cmd.toLowerCase().includes(searchTerm.toLowerCase()),
)
.map((cmd) => {
const commandConfig = castCommands.find(
(c) => c.name === cmd,
)
if (!commandConfig) return null
return (
<Button
key={cmd}
onClick={() => addCommand(cmd)}
className="w-full justify-start mb-2 text-left"
variant="ghost"
>
{cmd}
<Tooltip>
<TooltipTrigger>
<HelpCircle className="h-3 w-3 text-muted-foreground ml-1" />
</TooltipTrigger>
<TooltipContent>
<p>{commandConfig.description}</p>
</TooltipContent>
</Tooltip>
</Button>
)
})}
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</ScrollArea>
</div>
<div className="w-2/3 pl-4">
Expand All @@ -481,16 +607,17 @@ const CastTool: React.FC = () => {
</Button>
</div>
<ScrollArea className="h-[calc(100vh-8rem)]">
{activeCommands.map((cmd, index) => {
{activeCommands.map((cmd) => {
const commandConfig = castCommands.find((c) => c.name === cmd.name)
if (!commandConfig) return null
return (
<CastCommand
key={cmd.id}
id={cmd.id}
name={cmd.name}
description={commandConfig.description}
args={commandConfig.args}
onRemove={() => removeCommand(cmd.id)}
onRemove={removeCommand}
/>
)
})}
Expand Down
4 changes: 2 additions & 2 deletions src/components/ChainExtractor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import type React from 'react'
import { useCallback, useEffect, useMemo } from 'react'
import { toast } from 'sonner'
import type { IndexerOptions } from '../config/indexerOptions'
import type { CompactFreezeSummary, IndexerState } from '../store/indexerStore'
import { useIndexerStore } from '../store/indexerStore'
import type { CompactFreezeSummary, IndexerState } from '../stores/indexerStore'
import { useIndexerStore } from '../stores/indexerStore'
import AdvancedSettingsDialog, {
convertOptionsToBackend,
} from './AdvancedSettingsDialog'
Expand Down
4 changes: 3 additions & 1 deletion src/components/RPCInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ interface RPCInputProps {
value: string
onChange: (value: string) => void
placeholder?: string
className?: string
}

const RPCInput: React.FC<RPCInputProps> = ({
value,
onChange,
placeholder = 'Enter RPC URL',
className = '',
}) => {
const [showSuggestions, setShowSuggestions] = useState(false)
const [inputValue, setInputValue] = useState(value)
Expand Down Expand Up @@ -73,7 +75,7 @@ const RPCInput: React.FC<RPCInputProps> = ({
value={inputValue}
onChange={(e) => handleRPCChange(e.target.value)}
onFocus={() => setShowSuggestions(true)}
className="w-full"
className={`w-full ${className}`}
autoComplete="off"
autoCapitalize="off"
autoCorrect="off"
Expand Down
42 changes: 42 additions & 0 deletions src/stores/castToolStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { create } from 'zustand'
import { persist } from 'zustand/middleware'

interface CastCommand {
id: number
name: string
}

interface CastToolState {
activeCommands: CastCommand[]
nextId: number
searchTerm: string
addCommand: (name: string) => void
removeCommand: (id: number) => void
clearAllCommands: () => void
setSearchTerm: (term: string) => void
}

export const useCastToolStore = create<CastToolState>()(
persist(
(set) => ({
activeCommands: [],
nextId: 0,
searchTerm: '',
addCommand: (name) =>
set((state) => ({
activeCommands: [...state.activeCommands, { name, id: state.nextId }],
nextId: state.nextId + 1,
})),
removeCommand: (id) =>
set((state) => ({
activeCommands: state.activeCommands.filter((cmd) => cmd.id !== id),
})),
clearAllCommands: () => set({ activeCommands: [] }),
setSearchTerm: (term) => set({ searchTerm: term }),
}),
{
name: 'cast-tool-storage',
partialize: (state) => ({ activeCommands: state.activeCommands }),
}
)
)
File renamed without changes.

0 comments on commit 72ce90a

Please sign in to comment.