From 7c1fdd6aa19c50af23199c07d0b004fac5b622fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ti=E1=BA=BFn=20Nguy=E1=BB=85n=20Kh=E1=BA=AFc?= Date: Thu, 28 Nov 2024 00:21:43 +1300 Subject: [PATCH] feat: sharable extrinsic link --- src/index.css | 5 ++ src/routes/_layout/extrinsics.tsx | 97 ++++++++++++++++++------------- 2 files changed, 61 insertions(+), 41 deletions(-) diff --git a/src/index.css b/src/index.css index 7a6d5dd..8244b78 100644 --- a/src/index.css +++ b/src/index.css @@ -6,3 +6,8 @@ --dc-surface-color: var(--colors-bg-default); --dc-on-surface-color: var(--colors-fg-default); } + +/* TODO: remove once highlight bug of ParkUI is fixed */ +*::selection { + background: var(--colors-color-palette-default); +} diff --git a/src/routes/_layout/extrinsics.tsx b/src/routes/_layout/extrinsics.tsx index d9bd6e6..7cd82ef 100644 --- a/src/routes/_layout/extrinsics.tsx +++ b/src/routes/_layout/extrinsics.tsx @@ -9,17 +9,24 @@ import { toaster } from "../__root"; import type { Decoded, Shape } from "@polkadot-api/view-builder"; import { idle, pending } from "@reactive-dot/core"; import { SignerProvider, useMutation, useSigner } from "@reactive-dot/react"; -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, useNavigate } from "@tanstack/react-router"; +import { zodValidator } from "@tanstack/zod-adapter"; import SignATransactionIcon from "@w3f/polkadot-icons/solid/SignATransaction"; import { Binary } from "polkadot-api"; import { useCallback, useEffect, useMemo, useState } from "react"; import { css } from "styled-system/css"; +import { z } from "zod"; import { Select } from "~/components/select"; import { Button } from "~/components/ui/button"; import { Editable } from "~/components/ui/editable"; +const searchSchema = z.object({ + callData: z.string().optional(), +}); + export const Route = createFileRoute("/_layout/extrinsics")({ component: ExtrinsicPage, + validateSearch: zodValidator(searchSchema), }); type CallParamProps = { @@ -69,6 +76,34 @@ function CallParam({ const dynamicBuilder = useDynamicBuilder(); const viewBuilder = useViewBuilder(); + const navigate = useNavigate({ from: Route.fullPath }); + + const setCallDataInput = useCallback( + (callDataInput: string, forceRerender?: boolean) => { + if (callDataInput.trim() === "") { + return; + } + + try { + const decodedCall = viewBuilder.callDecoder(callDataInput); + + setDraftCallDataInput(callDataInput); + onChangePallet(decodedCall.pallet.value.idx); + onChangeCall(decodedCall.call.value.name); + setDefaultArgs(decodedCall.args.value); + navigate({ search: { callData: callDataInput } }); + + if (forceRerender) { + setArgsRenderCount((count) => count + 1); + } + } catch { + setDraftCallDataInput(callDataInput); + toaster.error({ id: callDataInput, title: "Invalid call data" }); + } + }, + [navigate, onChangeCall, onChangePallet, viewBuilder], + ); + const callData = useMemo(() => { if (args === INCOMPLETE || args === INVALID) { return undefined; @@ -88,6 +123,8 @@ function CallParam({ } }, [args, call, dynamicBuilder, pallet.name]); + const { callData: searchCallData } = Route.useSearch(); + const callDataHex = callData?.asHex(); const [defaultArgs, setDefaultArgs] = useState(); @@ -99,29 +136,19 @@ function CallParam({ const [callDataInput, _setCallDataInput] = useState(draftCallDataInput); - const setCallDataInput = useCallback( - (callDataInput: string, forceRerender?: boolean) => { - if (callDataInput.trim() === "") { - return; - } - - const decodedCall = viewBuilder.callDecoder(callDataInput); - - onChangePallet(decodedCall.pallet.value.idx); - onChangeCall(decodedCall.call.value.name); - setDefaultArgs(decodedCall.args.value); - - if (forceRerender) { - setArgsRenderCount((count) => count + 1); + useEffect( + () => { + if (searchCallData) { + setCallDataInput(searchCallData, true); } }, - [onChangeCall, onChangePallet, viewBuilder], + // eslint-disable-next-line react-hooks/exhaustive-deps + [], ); useEffect( () => { if (callDataHex !== undefined) { - setDraftCallDataInput(callDataHex); setCallDataInput(callDataHex); } }, @@ -144,14 +171,7 @@ function CallParam({ value={draftCallDataInput} onValueChange={(event) => setDraftCallDataInput(event.value)} onValueRevert={() => setDraftCallDataInput(callDataInput)} - onValueCommit={(event) => { - try { - setCallDataInput(event.value, true); - } catch { - setDraftCallDataInput(callDataInput); - toaster.error({ title: "Invalid call data" }); - } - }} + onValueCommit={(event) => setCallDataInput(event.value, true)} > Encoded call data @@ -228,13 +248,10 @@ function CallSelect({ pallet, onChangePallet }: CallSelectProps) { const defaultCallName = calls.at(0)!.name; - const [selectedCallName, setSelectedCallName] = useState(defaultCallName); - - useEffect(() => { - setSelectedCallName(defaultCallName); - }, [defaultCallName]); + const [_selectedCallName, setSelectedCallName] = useState(defaultCallName); - const selectedCall = calls.find((call) => call.name === selectedCallName); + const selectedCall = + calls.find((call) => call.name === _selectedCallName) ?? calls.at(0)!; const callItems = calls .map((call) => ({ @@ -248,18 +265,16 @@ function CallSelect({ pallet, onChangePallet }: CallSelectProps) {