Skip to content

Commit

Permalink
feat: use <InputBytes/> for [u8, n] type (#496)
Browse files Browse the repository at this point in the history
  • Loading branch information
peetzweg authored Jul 17, 2023
1 parent 205a21f commit 9867bc3
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 37 deletions.
79 changes: 48 additions & 31 deletions src/ui/components/form/InputBytes.tsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,72 @@
// Copyright 2022 @paritytech/contracts-ui authors & contributors
// SPDX-License-Identifier: GPL-3.0-only

import React, { useCallback, useState } from 'react';
import { hexToU8a, compactAddLength } from '@polkadot/util';
import { InputHex } from './InputHex';
import { hexToU8a } from '@polkadot/util';
import React, { useCallback, useMemo, useState } from 'react';
import { Input } from './Input';
import { classes } from 'helpers';
import { ArgComponentProps } from 'types';

type Props = ArgComponentProps<Uint8Array>;

type Props = ArgComponentProps<Uint8Array> & { length?: number };
type Validation = { isValid: boolean; message?: string };

function validate(value: string): Validation {
if (value.length % 2 !== 0) {
const isHexRegex = /^0x[A-F0-9]+$/i;
const validateFn =
(length = 64) =>
(value: string): Validation => {
if (!isHexRegex.test(value)) {
return { isValid: false, message: 'Not a valid hex string' };
}

const expectedLength = length + 2; // +2 for 0x prefix
if (value.length < expectedLength) {
return { isValid: false, message: `Input too short! Expecting ${length} characters.` };
}
if (value.length > expectedLength) {
return { isValid: false, message: `Input too long! Expecting ${length} characters.` };
}

return {
isValid: false,
message: 'A trailing zero will be added. Please input an even number of bytes.',
isValid: true,
message: '',
};
}
return {
isValid: true,
message: '',
};
}

export function InputBytes({
onChange,
className,
defaultValue,
}: Props): React.ReactElement<Props> {
export function InputBytes({ onChange, className, length }: Props): React.ReactElement<Props> {
const [value, setValue] = useState('');
const [{ isValid, message }, setValidation] = useState<Validation>({ isValid: true });
const validate = useMemo(() => validateFn(length), [length]);

const handleChange = useCallback(
(d: string) => {
setValue(d);
const validation = validate(d);
setValidation(validation);
try {
const raw = hexToU8a(`0x${d}`);
onChange(compactAddLength(raw));
} catch (e) {
console.error(e);
if (validation.isValid) {
try {
onChange(hexToU8a(d));
} catch (e) {
console.error(e);
}
} else {
// TODO shouldn't this unset the value in error and invalid case to prevent form submission?
}
},
[onChange]
[onChange, validate]
);

return (
<InputHex
defaultValue={defaultValue?.toString()}
onChange={handleChange}
className={className}
error={!isValid ? message : undefined}
/>
<>
<div className="relative flex w-full items-center">
<Input
className={classes('flex-1', className)}
onChange={handleChange}
placeholder="0x0000000000000000000000000000000000000000"
type="text"
value={value}
/>
</div>
{!isValid && <div className="mt-1 basis-full text-xs text-red-600">{message}</div>}
</>
);
}
25 changes: 19 additions & 6 deletions src/ui/components/form/findComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@
// SPDX-License-Identifier: GPL-3.0-only

import { AddressSelect } from '../account/Select';
import { Input } from './Input';
import { InputBalance } from './InputBalance';
import { Vector } from './Vector';
import { Bool } from './Bool';
import { Enum } from './Enum';
import { Input } from './Input';
import { InputBalance } from './InputBalance';
import { InputBn } from './InputBn';
import { InputBytes } from './InputBytes';
import { InputHash } from './InputHash';
import { Option } from './Option';
import { VectorFixed } from './VectorFixed';
import { Struct } from './Struct';
import { SubForm } from './SubForm';
import { Tuple } from './Tuple';
import { InputBn } from './InputBn';
import { InputHash } from './InputHash';
import { Vector } from './Vector';
import { VectorFixed } from './VectorFixed';
import { ArgComponentProps, Registry, TypeDef, TypeDefInfo } from 'types';

function subComponents(
Expand Down Expand Up @@ -102,6 +103,18 @@ export function findComponent(
}

if (type.info === TypeDefInfo.VecFixed) {
if (type.sub && !Array.isArray(type.sub)) {
switch (type.sub.type) {
case 'u8':
return (props: ArgComponentProps<Uint8Array>) => {
if (!type.length) throw new Error('Fixed Vector has no length');
return <InputBytes {...props} length={type.length * 2} />; // 2 hex chars per byte
};
default:
break;
}
}

const [component] = subComponents(registry, type, nestingNumber + 1);

return (props: React.PropsWithChildren<ArgComponentProps<unknown[]>>) => (
Expand Down

0 comments on commit 9867bc3

Please sign in to comment.