diff --git a/src/codec-components/LookupTypeEdit/BinaryDisplay.tsx b/src/codec-components/LookupTypeEdit/BinaryDisplay.tsx index 68c72a8..253f2a2 100644 --- a/src/codec-components/LookupTypeEdit/BinaryDisplay.tsx +++ b/src/codec-components/LookupTypeEdit/BinaryDisplay.tsx @@ -1,91 +1,182 @@ import { BinaryViewCodec } from "@/codec-components/BinaryViewCodec" -import { BinaryEditButton } from "@/components/BinaryEditButton" import { CopyText } from "@/components/Copy" import { ExpandBtn } from "@/components/Expand" import { CodecComponentType, NOTIN } from "@polkadot-api/react-builder" -import { HexString } from "@polkadot-api/substrate-bindings" -import { fromHex, toHex } from "polkadot-api/utils" -import { ComponentProps, FC, useState } from "react" +import { Binary, HexString } from "@polkadot-api/substrate-bindings" +import { ComponentProps, FC, useMemo, useState } from "react" import { twMerge } from "tailwind-merge" import { EditCodec } from "../EditCodec" import "./binaryDisplay.css" +type BinaryCodec = { + enc: (value: any | NOTIN) => Uint8Array + dec: (value: Uint8Array | HexString) => any | NOTIN +} +const toHex = (value: Uint8Array | HexString) => + typeof value === "string" ? value : Binary.toHex(value) + export const BinaryDisplay: FC< ComponentProps & { - codec: { - enc: (value: any | NOTIN) => Uint8Array - dec: (value: Uint8Array | HexString) => any | NOTIN - } + codec: BinaryCodec className?: string } > = ({ codecType, metadata, value, onUpdate, codec, className }) => { const [wrap, setWrap] = useState(false) - const encoded = (() => { - if (value.type === CodecComponentType.Initial) { - if (!value.value) return null - return typeof value.value === "string" - ? fromHex(value.value) - : value.value + const [draftInputValue, setDraftInputValue] = useState(null) + + const { hex, isEmpty, updatedInputValue } = useMemo(() => { + const encoded = (() => { + if (value.type === CodecComponentType.Initial) { + return value.value ?? null + } + if (value.value.empty || !value.value.encoded) return null + return value.value.encoded + })() + + const updatedInputValue = + value.type === CodecComponentType.Initial + ? value.value + ? toHex(value.value) + : "" + : value.value.empty + ? "" + : value.value.encoded + ? toHex(value.value.encoded) + : null + + return { + hex: encoded ? toHex(encoded) : null, + isEmpty: + (value.type === CodecComponentType.Initial && !value.value) || + (value.type === CodecComponentType.Updated && value.value.empty), + updatedInputValue, } - if (value.value.empty || !value.value.encoded) return null - return value.value.encoded - })() - const hex = encoded ? toHex(encoded) : null - const isEmpty = - (value.type === CodecComponentType.Initial && !value.value) || - (value.type === CodecComponentType.Updated && value.value.empty) + }, [value]) + + const inputValue = draftInputValue ?? updatedInputValue ?? "" + const { validity } = useMemo( + () => validateInputValue(inputValue, codec), + [codec, inputValue], + ) return (
-
- {isEmpty ? ( -
- Start by filling out the value, or enter a binary using the edit - binary button at the end of this line. -
- ) : ( - <> - 0x - - - )} +
+ { + const value = evt.currentTarget.value + if (evt.key === "Escape") { + setDraftInputValue(null) + } else if (evt.key === "Enter") { + if (value.trim() === "") { + onUpdate?.({ + empty: true, + }) + } else { + const validation = validateInputValue(value, codec) + if ( + validation.validity === "identic" || + validation.validity === "partial" + ) { + onUpdate?.({ + empty: false, + decoded: validation.decoded, + encoded: validation.encoded, + }) + } + } + setDraftInputValue(null) + } + }} + onChange={(evt) => { + const value = evt.target.value + const validation = validateInputValue(value, codec) + if (validation.validity === "identic") { + onUpdate?.({ + empty: false, + decoded: validation.decoded, + encoded: validation.encoded, + }) + } + setDraftInputValue(value) + }} + onBlur={() => setDraftInputValue(null)} + /> +
+ {isEmpty ? ( +
+ Start by filling out the value, or enter a binary clicking here +
+ ) : ( + <> + 0x + + + )} +
- setWrap((v) => !v)} - /> - { - const encoded = codec.enc(decoded) - onUpdate?.({ empty: false, decoded, encoded }) - return true - }} - decode={codec.dec} - iconProps={{ - size: 24, - }} - /> + > +
) } + +const validateInputValue = ( + value: string, + codec: BinaryCodec, +): + | { validity: "invalid" | "empty" } + | { + validity: "partial" | "identic" + decoded: any + encoded: Uint8Array + } => { + value = value.trim() + if (value.length < 4) return { validity: "empty" } + try { + const decoded = codec.dec(value) + const encoded = codec.enc(decoded) + return { + validity: toHex(encoded) === value ? "identic" : "partial", + decoded, + encoded, + } + } catch { + return { validity: "invalid" } + } +} diff --git a/src/hashParams.tsx b/src/hashParams.tsx index d71f930..2254733 100644 --- a/src/hashParams.tsx +++ b/src/hashParams.tsx @@ -92,12 +92,9 @@ export const useSyncHashParam = ( useLayoutEffect( () => - setHashParams( - { - [key]: getFn(...dependencies), - }, - location, - ), + setHashParams({ + [key]: getFn(...dependencies), + }), // eslint-disable-next-line react-hooks/exhaustive-deps dependencies, )