diff --git a/components/Select.tsx b/components/Select.tsx index 8c5e975..9ad7d9c 100644 --- a/components/Select.tsx +++ b/components/Select.tsx @@ -3,13 +3,11 @@ import { mauve, mauveDark, purple, purpleDark } from '@radix-ui/colors' import { useTheme } from 'next-themes' import { styled } from '../stitches.config' import dynamic from 'next/dynamic' -import type { Props } from 'react-select' +import type { Props, StylesConfig } from 'react-select' const SelectInput = dynamic(() => import('react-select'), { ssr: false }) +const CreatableSelectInput = dynamic(() => import('react-select/creatable'), { ssr: false }) -// eslint-disable-next-line react/display-name -const Select = forwardRef((props, ref) => { - const { theme } = useTheme() - const isDark = theme === 'dark' +const getColors = (isDark: boolean) => { const colors: any = { // primary: pink.pink9, active: isDark ? purpleDark.purple9 : purple.purple9, @@ -30,93 +28,136 @@ const Select = forwardRef((props, ref) => { } colors.outline = colors.background colors.selected = colors.secondary + return colors +} + +const getStyles = (isDark: boolean) => { + const colors = getColors(isDark) + const styles: StylesConfig = { + container: provided => { + return { + ...provided, + position: 'relative', + width: '100%' + } + }, + singleValue: provided => ({ + ...provided, + color: colors.mauve12 + }), + menu: provided => ({ + ...provided, + backgroundColor: colors.dropDownBg + }), + control: (provided, state) => { + return { + ...provided, + minHeight: 0, + border: '0px', + backgroundColor: colors.mauve4, + boxShadow: `0 0 0 1px ${state.isFocused ? colors.border : colors.secondary}` + } + }, + input: provided => { + return { + ...provided, + color: '$text' + } + }, + multiValue: provided => { + return { + ...provided, + backgroundColor: colors.mauve8 + } + }, + multiValueLabel: provided => { + return { + ...provided, + color: colors.mauve12 + } + }, + multiValueRemove: provided => { + return { + ...provided, + ':hover': { + background: colors.mauve9 + } + } + }, + option: (provided, state) => { + return { + ...provided, + color: colors.searchText, + backgroundColor: state.isFocused ? colors.activeLight : colors.dropDownBg, + ':hover': { + backgroundColor: colors.active, + color: '#ffffff' + }, + ':selected': { + backgroundColor: 'red' + } + } + }, + indicatorSeparator: provided => { + return { + ...provided, + backgroundColor: colors.secondary + } + }, + dropdownIndicator: (provided, state) => { + return { + ...provided, + padding: 6, + color: state.isFocused ? colors.border : colors.secondary, + ':hover': { + color: colors.border + } + } + }, + clearIndicator: provided => { + return { + ...provided, + padding: 6, + color: colors.secondary, + ':hover': { + color: colors.border + } + } + } + } + return styles +} + +// eslint-disable-next-line react/display-name +const Select = forwardRef((props, ref) => { + const { theme } = useTheme() + const isDark = theme === 'dark' + const styles = getStyles(isDark) return ( { - return { - ...provided, - position: 'relative' - } - }, - singleValue: provided => ({ - ...provided, - color: colors.mauve12 - }), - menu: provided => ({ - ...provided, - backgroundColor: colors.dropDownBg - }), - control: (provided, state) => { - return { - ...provided, - minHeight: 0, - border: '0px', - backgroundColor: colors.mauve4, - boxShadow: `0 0 0 1px ${state.isFocused ? colors.border : colors.secondary}` - } - }, - input: provided => { - return { - ...provided, - color: '$text' - } - }, - multiValue: provided => { - return { - ...provided, - backgroundColor: colors.mauve8 - } - }, - multiValueLabel: provided => { - return { - ...provided, - color: colors.mauve12 - } - }, - multiValueRemove: provided => { - return { - ...provided, - ':hover': { - background: colors.mauve9 - } - } - }, - option: (provided, state) => { - return { - ...provided, - color: colors.searchText, - backgroundColor: state.isFocused ? colors.activeLight : colors.dropDownBg, - ':hover': { - backgroundColor: colors.active, - color: '#ffffff' - }, - ':selected': { - backgroundColor: 'red' - } - } - }, - indicatorSeparator: provided => { - return { - ...provided, - backgroundColor: colors.secondary - } - }, - dropdownIndicator: (provided, state) => { - return { - ...provided, - color: state.isFocused ? colors.border : colors.secondary, - ':hover': { - color: colors.border - } - } - } - }} + styles={styles} + {...props} + /> + ) +}) + +// eslint-disable-next-line react/display-name +const Creatable = forwardRef((props, ref) => { + const { theme } = useTheme() + const isDark = theme === 'dark' + const styles = getStyles(isDark) + return ( + `Enter "${label}"`} + menuPosition={props.menuPosition || 'fixed'} + styles={styles} {...props} /> ) }) export default styled(Select, {}) +export const CreatableSelect = styled(Creatable, {}) diff --git a/components/Sequence.tsx b/components/Sequence.tsx new file mode 100644 index 0000000..b407e7b --- /dev/null +++ b/components/Sequence.tsx @@ -0,0 +1,71 @@ +import { FC, useCallback, useState } from 'react' +import state from '../state' +import { Flex, Input, Button } from '.' +import fetchAccountInfo from '../utils/accountInfo' +import { useSnapshot } from 'valtio' + +interface AccountSequenceProps { + address?: string +} +const AccountSequence: FC = ({ address }) => { + const { accounts } = useSnapshot(state) + const account = accounts.find(acc => acc.address === address) + const [isLoading, setIsLoading] = useState(false) + const setSequence = useCallback( + (sequence: number) => { + const acc = state.accounts.find(acc => acc.address == address) + if (!acc) return + acc.sequence = sequence + }, + [address] + ) + const handleUpdateSequence = useCallback( + async (silent?: boolean) => { + if (!account) return + setIsLoading(true) + + const info = await fetchAccountInfo(account.address, { silent }) + if (info) { + setSequence(info.Sequence) + } + + setIsLoading(false) + }, + [account, setSequence] + ) + const disabled = !account + return ( + + + + + ) +} + +export default AccountSequence diff --git a/components/SetHookDialog.tsx b/components/SetHookDialog.tsx index 28c85a0..b10d524 100644 --- a/components/SetHookDialog.tsx +++ b/components/SetHookDialog.tsx @@ -21,6 +21,7 @@ import { prepareDeployHookTx, sha256 } from '../state/actions/deployHook' import estimateFee from '../utils/estimateFee' import { getParameters, getInvokeOptions, transactionOptions, SetHookData } from '../utils/setHook' import { capitalize } from '../utils/helpers' +import AccountSequence from './Sequence' export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo( ({ accountAddress }) => { @@ -190,6 +191,10 @@ export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo( onChange={(acc: any) => setSelectedAccount(acc)} /> + + + + = ({ header, state: txState, ...props }) const prepareOptions = useCallback( (state: Partial = txState) => { - const { selectedTransaction, selectedDestAccount, selectedAccount, txFields, selectedFlags } = - state + const { + selectedTransaction, + selectedAccount, + txFields, + selectedFlags, + hookParameters, + memos + } = state const TransactionType = selectedTransaction?.value || null - const Destination = selectedDestAccount?.value || txFields?.Destination const Account = selectedAccount?.value || null const Flags = combineFlags(selectedFlags?.map(flag => flag.value)) || txFields?.Flags + const HookParameters = Object.entries(hookParameters || {}).reduce< + SetHookData['HookParameters'] + >((acc, [_, { label, value }]) => { + return acc.concat({ + HookParameter: { HookParameterName: toHex(label), HookParameterValue: value } + }) + }, []) + const Memos = memos + ? Object.entries(memos).reduce((acc, [_, { format, data, type }]) => { + return acc?.concat({ + Memo: { MemoData: data, MemoFormat: toHex(format), MemoType: toHex(type) } + }) + }, []) + : undefined return prepareTransaction({ ...txFields, + HookParameters, Flags, TransactionType, - Destination, - Account + Account, + Memos }) }, [txState] @@ -69,15 +90,29 @@ const Transaction: FC = ({ header, state: txState, ...props }) } }, [selectedAccount?.value, selectedTransaction?.value, setState, txIsLoading]) + const getJsonString = useCallback( + (state?: Partial) => + JSON.stringify(prepareOptions?.(state) || {}, null, editorSettings.tabSize), + [editorSettings.tabSize, prepareOptions] + ) + + const saveEditorState = useCallback( + (value: string = '', transactionType?: string) => { + const pTx = prepareState(value, transactionType) + if (pTx) { + pTx.editorValue = getJsonString(pTx) + return setState(pTx) + } + }, + [getJsonString, setState] + ) + const submitTest = useCallback(async () => { let st: TransactionState | undefined const tt = txState.selectedTransaction?.value if (viewType === 'json') { - // save the editor state first - const pst = prepareState(editorValue || '', tt) - if (!pst) return - - st = setState(pst) + st = saveEditorState(editorValue, tt) + if (!st) return } const account = accounts.find(acc => acc.address === selectedAccount?.value) @@ -90,11 +125,12 @@ const Transaction: FC = ({ header, state: txState, ...props }) throw Error('Account must be selected from imported accounts!') } const options = prepareOptions(st) - - const fields = getTxFields(options.TransactionType) - if (fields.Destination && !options.Destination) { - throw Error('Destination account is required!') - } + // delete unnecessary fields + Object.keys(options).forEach(field => { + if (!options[field]) { + delete options[field] + } + }) await sendTransaction(account, options, { logPrefix }) } catch (error) { @@ -108,23 +144,18 @@ const Transaction: FC = ({ header, state: txState, ...props }) } setState({ txIsLoading: false }) }, [ + txState.selectedTransaction?.value, viewType, accounts, txIsDisabled, setState, header, + saveEditorState, editorValue, - txState, selectedAccount?.value, prepareOptions ]) - const getJsonString = useCallback( - (state?: Partial) => - JSON.stringify(prepareOptions?.(state) || {}, null, editorSettings.tabSize), - [editorSettings.tabSize, prepareOptions] - ) - const resetState = useCallback( (transactionType: SelectOption | undefined = defaultTransactionType) => { const fields = getTxFields(transactionType?.value) @@ -134,13 +165,6 @@ const Transaction: FC = ({ header, state: txState, ...props }) selectedTransaction: transactionType } - if (fields.Destination !== undefined) { - nwState.selectedDestAccount = null - fields.Destination = '' - } else { - fields.Destination = undefined - } - if (transactionType?.value && transactionFlags[transactionType?.value] && fields.Flags) { nwState.selectedFlags = extractFlags(transactionType.value, fields.Flags) fields.Flags = undefined @@ -177,11 +201,21 @@ const Transaction: FC = ({ header, state: txState, ...props }) [accounts, prepareOptions, setState, txState] ) + const switchToJson = useCallback(() => { + const editorValue = getJsonString() + setState({ viewType: 'json', editorValue }) + }, [getJsonString, setState]) + + const switchToUI = useCallback(() => { + setState({ viewType: 'ui' }) + }, [setState]) + return ( {viewType === 'json' ? ( = ({ header, state: txState, ...props }) /> ) : ( = ({ header, state: txState, ...props })