Skip to content

Commit

Permalink
feat: sequence item duplication
Browse files Browse the repository at this point in the history
  • Loading branch information
tien committed Nov 27, 2024
1 parent 53ad9d7 commit ff934cf
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 35 deletions.
8 changes: 7 additions & 1 deletion src/components/account-select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { AccountListItem } from "./account-list-item";
import { Select } from "./select";
import type { WalletAccount } from "@reactive-dot/core/wallets.js";
import { useAccounts } from "@reactive-dot/react";
import { useState, type ReactNode } from "react";
import { useEffect, useState, type ReactNode } from "react";

type ControlledAccountSelectProps = {
accounts: WalletAccount[];
Expand Down Expand Up @@ -35,6 +35,12 @@ export function UnControlledAccountSelect({
const accounts = useAccounts();
const [account, setAccount] = useState(accounts.at(0));

useEffect(() => {
if (account === undefined && accounts.length > 0) {
setAccount(accounts.at(0));
}
}, [account, accounts]);

return (
<>
{children({
Expand Down
53 changes: 44 additions & 9 deletions src/components/param/sequence.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ import {
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import type {
Decoded,
SequenceDecoded,
SequenceShape,
} from "@polkadot-api/view-builder";
import AddIcon from "@w3f/polkadot-icons/solid/Add";
import CloseIcon from "@w3f/polkadot-icons/solid/Close";
import CopyIcon from "@w3f/polkadot-icons/solid/Copy";
import MoreMenuIcon from "@w3f/polkadot-icons/solid/MoreMenu";
import { type PropsWithChildren, useEffect, useMemo, useState } from "react";
import { css } from "styled-system/css";
Expand All @@ -42,6 +44,7 @@ export type SequenceParamProps<T> = ParamProps<T[]> & {
type SortableValue<T> = {
id: string;
value: T;
defaultValue?: Decoded | undefined;
};

export function SequenceParam<T>({
Expand All @@ -51,15 +54,20 @@ export function SequenceParam<T>({
}: SequenceParamProps<T>) {
const [length, setLength] = useState(defaultValue?.value.length ?? 1);

const [sortableValues, setSortableValues] = useState(
Array.from({ length }).map(
(): SortableValue<ParamInput<T>> => ({
id: globalThis.crypto.randomUUID(),
value: INCOMPLETE,
}),
),
const defaultValues = useMemo(
() =>
Array.from({ length }).map(
(_, index): SortableValue<ParamInput<T>> => ({
id: globalThis.crypto.randomUUID(),
value: INCOMPLETE,
defaultValue: defaultValue?.value.at(index),
}),
),
[defaultValue?.value, length],
);

const [sortableValues, setSortableValues] = useState(defaultValues);

const values = useMemo(
() => sortableValues.map((value) => value.value),
[sortableValues],
Expand Down Expand Up @@ -129,10 +137,22 @@ export function SequenceParam<T>({
values.filter((value) => value.id !== item.id),
)
}
onRequestDuplicate={() => {
setLength((length) => length + 1);
setSortableValues((values) => [
...values.slice(0, index + 1),
{
id: globalThis.crypto.randomUUID(),
value: values.at(index)?.value ?? INCOMPLETE,
defaultValue: defaultValues.at(index)?.defaultValue,
},
...values.slice(index + 1),
]);
}}
>
<CodecParam
shape={sequence.shape}
defaultValue={defaultValue?.value.at(index)}
defaultValue={item.defaultValue}
onChangeValue={(value) =>
setSortableValues((array) =>
array.with(index, {
Expand Down Expand Up @@ -165,12 +185,14 @@ type SortableItemProps = PropsWithChildren<{
id: string;
sortable?: boolean;
onRequestRemove: () => void;
onRequestDuplicate: () => void;
}>;

export function SortableItem({
id,
sortable,
onRequestRemove,
onRequestDuplicate,
children,
}: SortableItemProps) {
const { attributes, listeners, setNodeRef, transform, transition } =
Expand Down Expand Up @@ -214,9 +236,22 @@ export function SortableItem({
</div>
</IconButton>
)}
<IconButton variant="ghost" size="xs" onClick={onRequestRemove}>
<IconButton
title="Delete item"
variant="ghost"
size="xs"
onClick={onRequestRemove}
>
<CloseIcon fill="currentcolor" />
</IconButton>
<IconButton
title="Duplicate item"
variant="ghost"
size="xs"
onClick={onRequestDuplicate}
>
<CopyIcon fill="currentcolor" />
</IconButton>
</div>
</div>
);
Expand Down
64 changes: 39 additions & 25 deletions src/routes/_layout/extrinsics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { SignerProvider, useMutation, useSigner } from "@reactive-dot/react";
import { createFileRoute } from "@tanstack/react-router";
import SignATransactionIcon from "@w3f/polkadot-icons/solid/SignATransaction";
import { Binary } from "polkadot-api";
import { useEffect, useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { css } from "styled-system/css";
import { Select } from "~/components/select";
import { Button } from "~/components/ui/button";
Expand Down Expand Up @@ -69,11 +69,6 @@ function CallParam({
const dynamicBuilder = useDynamicBuilder();
const viewBuilder = useViewBuilder();

const [defaultArgs, setDefaultArgs] = useState<{
id: string;
decoded: Decoded;
}>();

const callData = useMemo(() => {
if (args === INCOMPLETE || args === INVALID) {
return undefined;
Expand All @@ -95,24 +90,51 @@ function CallParam({

const callDataHex = callData?.asHex();

const [defaultArgs, setDefaultArgs] = useState<Decoded>();
const [argsRenderCount, setArgsRenderCount] = useState(0);

const [draftCallDataInput, setDraftCallDataInput] = useState(
callData?.asHex() ?? "",
callDataHex ?? "",
);
const [callDataInput, setCallDataInput] = useState(draftCallDataInput);

useEffect(() => {
if (callDataHex !== undefined) {
setDraftCallDataInput(callDataHex);
setCallDataInput(callDataHex);
}
}, [callDataHex]);
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);
}
},
[onChangeCall, onChangePallet, viewBuilder],
);

useEffect(
() => {
if (callDataHex !== undefined) {
setDraftCallDataInput(callDataHex);
setCallDataInput(callDataHex);
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[callDataHex],
);

return (
<div className={css({ gridArea: "param-and-submit" })}>
<CodecParam
key={defaultArgs?.id}
key={argsRenderCount}
shape={param}
defaultValue={defaultArgs?.decoded}
defaultValue={defaultArgs}
onChangeValue={setArgs}
/>
<hr className={css({ margin: "2rem 0 1rem 0" })} />
Expand All @@ -124,15 +146,7 @@ function CallParam({
onValueRevert={() => setDraftCallDataInput(callDataInput)}
onValueCommit={(event) => {
try {
const decodedCall = viewBuilder.callDecoder(event.value);

onChangePallet(decodedCall.pallet.value.idx);
onChangeCall(decodedCall.call.value.name);
setDefaultArgs({
id: globalThis.crypto.randomUUID(),
decoded: decodedCall.args.value,
});
setCallDataInput(draftCallDataInput);
setCallDataInput(event.value, true);
} catch {
setDraftCallDataInput(callDataInput);
toaster.error({ title: "Invalid call data" });
Expand Down

0 comments on commit ff934cf

Please sign in to comment.