diff --git a/bun.lockb b/bun.lockb index 1e60e92..d418546 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 3a9f346..7f4d370 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/CastTool.tsx b/src/components/CastTool.tsx index d05eafd..ccdc801 100644 --- a/src/components/CastTool.tsx +++ b/src/components/CastTool.tsx @@ -1,6 +1,15 @@ 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' @@ -8,13 +17,15 @@ 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 = ({ + id, name, description, args, @@ -24,9 +35,11 @@ const CastCommand: React.FC = ({ const [output, setOutput] = useState('') 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 () => { @@ -61,23 +74,36 @@ const CastCommand: React.FC = ({ -
{args.map((arg, index) => ( -
-
@@ -94,11 +120,14 @@ const CastCommand: React.FC = ({ } 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 = [ { @@ -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 (
@@ -461,16 +560,43 @@ const CastTool: React.FC = () => { className="mb-4" /> - {filteredCommands.map((cmd) => ( - - ))} + + {commandCategories.map((category) => ( + + {category.name} + + {category.commands + .filter((cmd) => + cmd.toLowerCase().includes(searchTerm.toLowerCase()), + ) + .map((cmd) => { + const commandConfig = castCommands.find( + (c) => c.name === cmd, + ) + if (!commandConfig) return null + return ( + + ) + })} + + + ))} +
@@ -481,16 +607,17 @@ const CastTool: React.FC = () => {
- {activeCommands.map((cmd, index) => { + {activeCommands.map((cmd) => { const commandConfig = castCommands.find((c) => c.name === cmd.name) if (!commandConfig) return null return ( removeCommand(cmd.id)} + onRemove={removeCommand} /> ) })} diff --git a/src/components/ChainExtractor.tsx b/src/components/ChainExtractor.tsx index f9f788e..d87e819 100644 --- a/src/components/ChainExtractor.tsx +++ b/src/components/ChainExtractor.tsx @@ -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' diff --git a/src/components/RPCInput.tsx b/src/components/RPCInput.tsx index 5a2f630..0ea373d 100644 --- a/src/components/RPCInput.tsx +++ b/src/components/RPCInput.tsx @@ -6,12 +6,14 @@ interface RPCInputProps { value: string onChange: (value: string) => void placeholder?: string + className?: string } const RPCInput: React.FC = ({ value, onChange, placeholder = 'Enter RPC URL', + className = '', }) => { const [showSuggestions, setShowSuggestions] = useState(false) const [inputValue, setInputValue] = useState(value) @@ -73,7 +75,7 @@ const RPCInput: React.FC = ({ value={inputValue} onChange={(e) => handleRPCChange(e.target.value)} onFocus={() => setShowSuggestions(true)} - className="w-full" + className={`w-full ${className}`} autoComplete="off" autoCapitalize="off" autoCorrect="off" diff --git a/src/stores/castToolStore.ts b/src/stores/castToolStore.ts new file mode 100644 index 0000000..f75fd61 --- /dev/null +++ b/src/stores/castToolStore.ts @@ -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()( + 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 }), + } + ) +) \ No newline at end of file diff --git a/src/store/indexerStore.ts b/src/stores/indexerStore.ts similarity index 100% rename from src/store/indexerStore.ts rename to src/stores/indexerStore.ts