Compare commits
1 Commits
feat/debug
...
test-page
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5b918d877 |
@@ -350,7 +350,7 @@ const Accounts: FC<AccountProps> = props => {
|
|||||||
borderBottom: props.card ? "1px solid $mauve6" : undefined,
|
borderBottom: props.card ? "1px solid $mauve6" : undefined,
|
||||||
"@hover": {
|
"@hover": {
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
background: "$backgroundAlt",
|
background: "$mauve3",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useSnapshot } from "valtio";
|
|
||||||
import { Select } from ".";
|
|
||||||
import state from "../state";
|
|
||||||
import LogBox from "./LogBox";
|
|
||||||
import Text from "./Text";
|
|
||||||
|
|
||||||
const DebugStream = () => {
|
|
||||||
const snap = useSnapshot(state);
|
|
||||||
|
|
||||||
const accountOptions = snap.accounts.map(acc => ({
|
|
||||||
label: acc.name,
|
|
||||||
value: acc.address,
|
|
||||||
}));
|
|
||||||
const [selectedAccount, setSelectedAccount] = useState<typeof accountOptions[0] | null>(null);
|
|
||||||
|
|
||||||
const renderNav = () => (
|
|
||||||
<>
|
|
||||||
<Text css={{ mx: "$2", fontSize: "inherit" }}>Account: </Text>
|
|
||||||
<Select
|
|
||||||
instanceId="debugStreamAccount"
|
|
||||||
placeholder="Select account"
|
|
||||||
options={accountOptions}
|
|
||||||
hideSelectedOptions
|
|
||||||
value={selectedAccount}
|
|
||||||
onChange={acc => setSelectedAccount(acc as any)}
|
|
||||||
css={{ width: "30%" }}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const account = selectedAccount?.value;
|
|
||||||
if (!account) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const socket = new WebSocket(`wss://hooks-testnet-debugstream.xrpl-labs.com/${account}`);
|
|
||||||
|
|
||||||
const onOpen = () => {
|
|
||||||
state.debugLogs = [];
|
|
||||||
state.debugLogs.push({
|
|
||||||
type: "success",
|
|
||||||
message: `Debug stream opened for account ${account}`,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const onError = () => {
|
|
||||||
state.debugLogs.push({
|
|
||||||
type: "error",
|
|
||||||
message: "Something went wrong in establishing connection!",
|
|
||||||
});
|
|
||||||
setSelectedAccount(null);
|
|
||||||
};
|
|
||||||
const onMessage = (event: any) => {
|
|
||||||
if (!event.data) return;
|
|
||||||
state.debugLogs.push({
|
|
||||||
type: "log",
|
|
||||||
message: event.data,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
socket.addEventListener("open", onOpen);
|
|
||||||
socket.addEventListener("close", onError);
|
|
||||||
socket.addEventListener("error", onError);
|
|
||||||
socket.addEventListener("message", onMessage);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
socket.removeEventListener("open", onOpen);
|
|
||||||
socket.removeEventListener("close", onError);
|
|
||||||
socket.removeEventListener("message", onMessage);
|
|
||||||
|
|
||||||
socket.close();
|
|
||||||
};
|
|
||||||
}, [selectedAccount]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LogBox
|
|
||||||
enhanced
|
|
||||||
renderNav={renderNav}
|
|
||||||
title="Debug stream"
|
|
||||||
logs={snap.debugLogs}
|
|
||||||
clearLog={() => (state.debugLogs = [])}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DebugStream;
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useRef, useLayoutEffect, ReactNode } from "react";
|
import React, { useRef, useLayoutEffect } from "react";
|
||||||
import { Notepad, Prohibit } from "phosphor-react";
|
import { Notepad, Prohibit } from "phosphor-react";
|
||||||
import useStayScrolled from "react-stay-scrolled";
|
import useStayScrolled from "react-stay-scrolled";
|
||||||
import NextLink from "next/link";
|
import NextLink from "next/link";
|
||||||
@@ -17,11 +17,9 @@ interface ILogBox {
|
|||||||
title: string;
|
title: string;
|
||||||
clearLog?: () => void;
|
clearLog?: () => void;
|
||||||
logs: ILog[];
|
logs: ILog[];
|
||||||
renderNav?: () => ReactNode;
|
|
||||||
enhanced?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children, renderNav, enhanced }) => {
|
const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children }) => {
|
||||||
const logRef = useRef<HTMLPreElement>(null);
|
const logRef = useRef<HTMLPreElement>(null);
|
||||||
const { stayScrolled /*, scrollBottom*/ } = useStayScrolled(logRef);
|
const { stayScrolled /*, scrollBottom*/ } = useStayScrolled(logRef);
|
||||||
|
|
||||||
@@ -41,7 +39,7 @@ const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children, renderNav,
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Container css={{ px: 0, flexShrink: 1 }}>
|
<Container css={{ px: 0, flexShrink: 1 }}>
|
||||||
<Flex css={{ py: "$3", alignItems: "center", fontSize: "$sm", fontWeight: 300 }}>
|
<Flex css={{ py: "$3" }}>
|
||||||
<Heading
|
<Heading
|
||||||
as="h3"
|
as="h3"
|
||||||
css={{
|
css={{
|
||||||
@@ -58,7 +56,6 @@ const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children, renderNav,
|
|||||||
>
|
>
|
||||||
<Notepad size="15px" /> <Text css={{ lineHeight: 1 }}>{title}</Text>
|
<Notepad size="15px" /> <Text css={{ lineHeight: 1 }}>{title}</Text>
|
||||||
</Heading>
|
</Heading>
|
||||||
{renderNav?.()}
|
|
||||||
<Flex css={{ ml: "auto", gap: "$3", marginRight: "$3" }}>
|
<Flex css={{ ml: "auto", gap: "$3", marginRight: "$3" }}>
|
||||||
{clearLog && (
|
{clearLog && (
|
||||||
<Button ghost size="xs" onClick={clearLog}>
|
<Button ghost size="xs" onClick={clearLog}>
|
||||||
@@ -87,18 +84,10 @@ const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children, renderNav,
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{logs?.map((log, index) => (
|
{logs?.map((log, index) => (
|
||||||
<Box
|
<Box as="span" key={log.type + index}>
|
||||||
as="span"
|
{/* <LogText capitalize variant={log.type}>
|
||||||
key={log.type + index}
|
{log.type}:{" "}
|
||||||
css={{
|
</LogText> */}
|
||||||
"@hover": {
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: enhanced ? "$backgroundAlt" : undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
p: "$2 $1",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<LogText variant={log.type}>
|
<LogText variant={log.type}>
|
||||||
{log.message}{" "}
|
{log.message}{" "}
|
||||||
{log.link && (
|
{log.link && (
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ const Text = styled("span", {
|
|||||||
fontFamily: "$monospace",
|
fontFamily: "$monospace",
|
||||||
lineHeight: "$body",
|
lineHeight: "$body",
|
||||||
color: "$text",
|
color: "$text",
|
||||||
wordWrap: 'break-word',
|
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
log: {
|
log: {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { gray, grayDark } from "@radix-ui/colors";
|
import { mauve, mauveDark } from "@radix-ui/colors";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { styled } from '../stitches.config';
|
import { styled } from '../stitches.config';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
@@ -11,17 +11,16 @@ const Select: FC<Props> = props => {
|
|||||||
const isDark = theme === "dark";
|
const isDark = theme === "dark";
|
||||||
const colors: any = {
|
const colors: any = {
|
||||||
// primary: pink.pink9,
|
// primary: pink.pink9,
|
||||||
primary: isDark ? grayDark.gray4 : gray.gray4,
|
primary: isDark ? mauveDark.mauve4 : mauve.mauve4,
|
||||||
secondary: isDark ? grayDark.gray8 : gray.gray8,
|
secondary: isDark ? mauveDark.mauve8 : mauve.mauve8,
|
||||||
background: isDark ? "rgb(10, 10, 10)" : "rgb(244, 244, 244)",
|
background: isDark ? "rgb(10, 10, 10)" : "rgb(245, 245, 245)",
|
||||||
searchText: isDark ? grayDark.gray12 : gray.gray12,
|
searchText: isDark ? mauveDark.mauve12 : mauve.mauve12,
|
||||||
placeholder: isDark ? grayDark.gray11 : gray.gray11,
|
placeholder: isDark ? mauveDark.mauve11 : mauve.mauve11,
|
||||||
};
|
};
|
||||||
colors.outline = colors.background;
|
colors.outline = colors.background;
|
||||||
colors.selected = colors.secondary;
|
colors.selected = colors.secondary;
|
||||||
return (
|
return (
|
||||||
<SelectInput
|
<SelectInput
|
||||||
menuPosition="fixed"
|
|
||||||
theme={theme => ({
|
theme={theme => ({
|
||||||
...theme,
|
...theme,
|
||||||
spacing: {
|
spacing: {
|
||||||
|
|||||||
@@ -1,221 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"TransactionType": "AccountDelete",
|
|
||||||
"Account": "rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm",
|
|
||||||
"Destination": "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe",
|
|
||||||
"DestinationTag": 13,
|
|
||||||
"Fee": "2000000",
|
|
||||||
"Sequence": 2470665,
|
|
||||||
"Flags": 2147483648
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TransactionType": "AccountSet",
|
|
||||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
||||||
"Fee": "12",
|
|
||||||
"Sequence": 5,
|
|
||||||
"Domain": "6578616D706C652E636F6D",
|
|
||||||
"SetFlag": 5,
|
|
||||||
"MessageKey": "03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Account": "rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo",
|
|
||||||
"TransactionType": "CheckCancel",
|
|
||||||
"CheckID": "49647F0D748DC3FE26BDACBC57F251AADEFFF391403EC9BF87C97F67E9977FB0",
|
|
||||||
"Fee": "12"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Account": "rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy",
|
|
||||||
"TransactionType": "CheckCash",
|
|
||||||
"Amount": {
|
|
||||||
"value": "100",
|
|
||||||
"type": "currency"
|
|
||||||
},
|
|
||||||
"CheckID": "838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334",
|
|
||||||
"Fee": "12"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TransactionType": "CheckCreate",
|
|
||||||
"Account": "rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo",
|
|
||||||
"Destination": "rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy",
|
|
||||||
"SendMax": "100000000",
|
|
||||||
"Expiration": 570113521,
|
|
||||||
"InvoiceID": "6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B",
|
|
||||||
"DestinationTag": 1,
|
|
||||||
"Fee": "12"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TransactionType": "DepositPreauth",
|
|
||||||
"Account": "rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8",
|
|
||||||
"Authorize": "rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de",
|
|
||||||
"Fee": "10",
|
|
||||||
"Flags": 2147483648,
|
|
||||||
"Sequence": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
||||||
"TransactionType": "EscrowCancel",
|
|
||||||
"Owner": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
||||||
"OfferSequence": 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
||||||
"TransactionType": "EscrowCreate",
|
|
||||||
"Amount": {
|
|
||||||
"value": "100",
|
|
||||||
"type": "currency"
|
|
||||||
},
|
|
||||||
"Destination": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
|
|
||||||
"CancelAfter": 533257958,
|
|
||||||
"FinishAfter": 533171558,
|
|
||||||
"Condition": "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100",
|
|
||||||
"DestinationTag": 23480,
|
|
||||||
"SourceTag": 11747
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
||||||
"TransactionType": "EscrowFinish",
|
|
||||||
"Owner": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
||||||
"OfferSequence": 7,
|
|
||||||
"Condition": "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100",
|
|
||||||
"Fulfillment": "A0028000"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TransactionType": "NFTokenBurn",
|
|
||||||
"Account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
|
||||||
"Fee": "10",
|
|
||||||
"TokenID": "000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TransactionType": "NFTokenAcceptOffer",
|
|
||||||
"Fee": "10"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TransactionType": "NFTokenCancelOffer",
|
|
||||||
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
|
||||||
"TokenIDs": "000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TransactionType": "NFTokenCreateOffer",
|
|
||||||
"Account": "rs8jBmmfpwgmrSPgwMsh7CvKRmRt1JTVSX",
|
|
||||||
"TokenID": "000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007",
|
|
||||||
"Amount": {
|
|
||||||
"value": "100",
|
|
||||||
"type": "currency"
|
|
||||||
},
|
|
||||||
"Flags": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TransactionType": "OfferCancel",
|
|
||||||
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
|
||||||
"Fee": "12",
|
|
||||||
"Flags": 0,
|
|
||||||
"LastLedgerSequence": 7108629,
|
|
||||||
"OfferSequence": 6,
|
|
||||||
"Sequence": 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TransactionType": "OfferCreate",
|
|
||||||
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
|
||||||
"Fee": "12",
|
|
||||||
"Flags": 0,
|
|
||||||
"LastLedgerSequence": 7108682,
|
|
||||||
"Sequence": 8,
|
|
||||||
"TakerGets": "6000000",
|
|
||||||
"Amount": {
|
|
||||||
"value": "100",
|
|
||||||
"type": "currency"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TransactionType": "Payment",
|
|
||||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
||||||
"Destination": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
|
||||||
"Amount": {
|
|
||||||
"value": "100",
|
|
||||||
"type": "currency"
|
|
||||||
},
|
|
||||||
"Fee": "12",
|
|
||||||
"Flags": 2147483648,
|
|
||||||
"Sequence": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
||||||
"TransactionType": "PaymentChannelCreate",
|
|
||||||
"Amount": {
|
|
||||||
"value": "100",
|
|
||||||
"type": "currency"
|
|
||||||
},
|
|
||||||
"Destination": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
|
|
||||||
"SettleDelay": 86400,
|
|
||||||
"PublicKey": "32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A",
|
|
||||||
"CancelAfter": 533171558,
|
|
||||||
"DestinationTag": 23480,
|
|
||||||
"SourceTag": 11747
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
||||||
"TransactionType": "PaymentChannelFund",
|
|
||||||
"Channel": "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198",
|
|
||||||
"Amount": {
|
|
||||||
"value": "200",
|
|
||||||
"type": "currency"
|
|
||||||
},
|
|
||||||
"Expiration": 543171558
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Flags": 0,
|
|
||||||
"TransactionType": "SetRegularKey",
|
|
||||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
||||||
"Fee": "12",
|
|
||||||
"RegularKey": "rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Flags": 0,
|
|
||||||
"TransactionType": "SignerListSet",
|
|
||||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
||||||
"Fee": "12",
|
|
||||||
"SignerQuorum": 3,
|
|
||||||
"SignerEntries": {
|
|
||||||
"type": "json",
|
|
||||||
"value": [
|
|
||||||
{
|
|
||||||
"SignerEntry": {
|
|
||||||
"Account": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
|
|
||||||
"SignerWeight": 2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"SignerEntry": {
|
|
||||||
"Account": "rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v",
|
|
||||||
"SignerWeight": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"SignerEntry": {
|
|
||||||
"Account": "raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n",
|
|
||||||
"SignerWeight": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TransactionType": "TicketCreate",
|
|
||||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
||||||
"Fee": "10",
|
|
||||||
"Sequence": 381,
|
|
||||||
"TicketCount": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TransactionType": "TrustSet",
|
|
||||||
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
|
||||||
"Fee": "12",
|
|
||||||
"Flags": 262144,
|
|
||||||
"LastLedgerSequence": 8007750,
|
|
||||||
"Amount": {
|
|
||||||
"value": "100",
|
|
||||||
"type": "currency"
|
|
||||||
},
|
|
||||||
"Sequence": 12
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -17,7 +17,6 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const slug = router.query?.slug;
|
const slug = router.query?.slug;
|
||||||
const gistId = (Array.isArray(slug) && slug[0]) ?? null;
|
const gistId = (Array.isArray(slug) && slug[0]) ?? null;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (gistId && router.isReady) {
|
if (gistId && router.isReady) {
|
||||||
fetchFiles(gistId);
|
fetchFiles(gistId);
|
||||||
|
|||||||
@@ -1,15 +1,6 @@
|
|||||||
import { Container, Flex, Box, Tabs, Tab, Input, Select, Text, Button } from "../../components";
|
import { Container, Flex, Box, Tabs, Tab, Input, Select, Text, Button } from "../../components";
|
||||||
import { Play } from "phosphor-react";
|
import { Play } from "phosphor-react";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { useSnapshot } from "valtio";
|
|
||||||
import state from "../../state";
|
|
||||||
import { sendTransaction } from "../../state/actions";
|
|
||||||
import { useCallback, useEffect, useState } from "react";
|
|
||||||
import transactionsData from "../../content/transactions.json";
|
|
||||||
|
|
||||||
const DebugStream = dynamic(() => import("../../components/DebugStream"), {
|
|
||||||
ssr: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const LogBox = dynamic(() => import("../../components/LogBox"), {
|
const LogBox = dynamic(() => import("../../components/LogBox"), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
@@ -18,280 +9,82 @@ const Accounts = dynamic(() => import("../../components/Accounts"), {
|
|||||||
ssr: false,
|
ssr: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// type SelectOption<T> = { value: T, label: string };
|
|
||||||
type TxFields = Omit<typeof transactionsData[0], "Account" | "Sequence" | "TransactionType">;
|
|
||||||
type OtherFields = (keyof Omit<TxFields, "Destination">)[];
|
|
||||||
|
|
||||||
const Transaction = () => {
|
const Transaction = () => {
|
||||||
const snap = useSnapshot(state);
|
const options = [
|
||||||
|
{ value: "chocolate", label: "Chocolate" },
|
||||||
const transactionsOptions = transactionsData.map(tx => ({
|
{ value: "strawberry", label: "Strawberry" },
|
||||||
value: tx.TransactionType,
|
{ value: "vanilla", label: "Vanilla" },
|
||||||
label: tx.TransactionType,
|
{
|
||||||
}));
|
value: "long",
|
||||||
const [selectedTransaction, setSelectedTransaction] = useState<
|
label: "lorem woy uiwyf wyfw8 fwfw98f w98fy wf8fw89f 9w8fy w9fyw9f wf7tdw9f ",
|
||||||
typeof transactionsOptions[0] | null
|
},
|
||||||
>(null);
|
];
|
||||||
|
|
||||||
const accountOptions = snap.accounts.map(acc => ({
|
|
||||||
label: acc.name,
|
|
||||||
value: acc.address,
|
|
||||||
}));
|
|
||||||
const [selectedAccount, setSelectedAccount] = useState<typeof accountOptions[0] | null>(null);
|
|
||||||
|
|
||||||
const destAccountOptions = snap.accounts
|
|
||||||
.map(acc => ({
|
|
||||||
label: acc.name,
|
|
||||||
value: acc.address,
|
|
||||||
}))
|
|
||||||
.filter(acc => acc.value !== selectedAccount?.value);
|
|
||||||
const [selectedDestAccount, setSelectedDestAccount] = useState<
|
|
||||||
typeof destAccountOptions[0] | null
|
|
||||||
>(null);
|
|
||||||
|
|
||||||
const [txIsLoading, setTxIsLoading] = useState(false);
|
|
||||||
const [txIsDisabled, setTxIsDisabled] = useState(false);
|
|
||||||
const [txFields, setTxFields] = useState<TxFields>({});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const transactionType = selectedTransaction?.value;
|
|
||||||
const account = snap.accounts.find(acc => acc.address === selectedAccount?.value);
|
|
||||||
if (!account || !transactionType || txIsLoading) {
|
|
||||||
setTxIsDisabled(true);
|
|
||||||
} else {
|
|
||||||
setTxIsDisabled(false);
|
|
||||||
}
|
|
||||||
}, [txIsLoading, selectedTransaction, selectedAccount, snap.accounts]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let _txFields: TxFields | undefined = transactionsData.find(
|
|
||||||
tx => tx.TransactionType === selectedTransaction?.value
|
|
||||||
);
|
|
||||||
if (!_txFields) return setTxFields({});
|
|
||||||
_txFields = { ..._txFields } as TxFields;
|
|
||||||
|
|
||||||
setSelectedDestAccount(null);
|
|
||||||
// @ts-ignore
|
|
||||||
delete _txFields.TransactionType;
|
|
||||||
// @ts-ignore
|
|
||||||
delete _txFields.Account;
|
|
||||||
// @ts-ignore
|
|
||||||
delete _txFields.Sequence;
|
|
||||||
setTxFields(_txFields);
|
|
||||||
}, [selectedTransaction, setSelectedDestAccount]);
|
|
||||||
|
|
||||||
const submitTest = useCallback(async () => {
|
|
||||||
const account = snap.accounts.find(acc => acc.address === selectedAccount?.value);
|
|
||||||
const TransactionType = selectedTransaction?.value;
|
|
||||||
if (!account || !TransactionType || txIsDisabled) return;
|
|
||||||
|
|
||||||
setTxIsLoading(true);
|
|
||||||
// setTxIsError(null)
|
|
||||||
try {
|
|
||||||
let options = { ...txFields };
|
|
||||||
|
|
||||||
options.Destination = selectedDestAccount?.value;
|
|
||||||
(Object.keys(options) as (keyof TxFields)[]).forEach(field => {
|
|
||||||
let _value = options[field];
|
|
||||||
// convert currency
|
|
||||||
if (typeof _value === "object" && _value.type === "currency") {
|
|
||||||
if (+_value.value) {
|
|
||||||
options[field] = (+_value.value * 1000000 + "") as any;
|
|
||||||
} else {
|
|
||||||
options[field] = undefined; // 👇 💀
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// handle type: `json`
|
|
||||||
if (typeof _value === "object" && _value.type === "json") {
|
|
||||||
if (typeof _value.value === "object") {
|
|
||||||
options[field] = _value.value as any;
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
options[field] = JSON.parse(_value.value);
|
|
||||||
} catch (error) {
|
|
||||||
const message = `Input error for json field '${field}': ${
|
|
||||||
error instanceof Error ? error.message : ""
|
|
||||||
}`;
|
|
||||||
throw Error(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete unneccesary fields
|
|
||||||
if (!options[field]) {
|
|
||||||
delete options[field];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await sendTransaction(account, {
|
|
||||||
TransactionType,
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
if (error instanceof Error) {
|
|
||||||
state.transactionLogs.push({ type: "error", message: error.message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setTxIsLoading(false);
|
|
||||||
}, [
|
|
||||||
selectedAccount,
|
|
||||||
selectedDestAccount,
|
|
||||||
selectedTransaction,
|
|
||||||
snap.accounts,
|
|
||||||
txFields,
|
|
||||||
txIsDisabled,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const resetState = useCallback(() => {
|
|
||||||
setSelectedAccount(null);
|
|
||||||
setSelectedDestAccount(null);
|
|
||||||
setSelectedTransaction(null);
|
|
||||||
setTxFields({});
|
|
||||||
setTxIsDisabled(false);
|
|
||||||
setTxIsLoading(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const usualFields = ["TransactionType", "Amount", "Account", "Destination"];
|
|
||||||
const otherFields = Object.keys(txFields).filter(k => !usualFields.includes(k)) as OtherFields;
|
|
||||||
return (
|
return (
|
||||||
<Box css={{ position: "relative", height: "calc(100% - 28px)" }}>
|
<>
|
||||||
<Container css={{ p: "$3 0", fontSize: "$sm", height: "calc(100% - 28px)" }}>
|
<Container css={{ p: "$3 0", fontSize: "$sm" }}>
|
||||||
<Flex column fluid css={{ height: "100%", overflowY: "auto" }}>
|
<Flex column fluid>
|
||||||
<Flex row fluid css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}>
|
<Flex row fluid css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}>
|
||||||
<Text muted css={{ mr: "$3" }}>
|
<Text muted css={{ mr: "$3" }}>
|
||||||
Transaction type:{" "}
|
Transaction type:{" "}
|
||||||
</Text>
|
</Text>
|
||||||
<Select
|
<Select
|
||||||
instanceId="transactionsType"
|
instanceId="transaction"
|
||||||
placeholder="Select transaction type"
|
placeholder="Select transaction type"
|
||||||
options={transactionsOptions}
|
|
||||||
hideSelectedOptions
|
|
||||||
css={{ width: "70%" }}
|
css={{ width: "70%" }}
|
||||||
value={selectedTransaction}
|
options={options}
|
||||||
onChange={tt => setSelectedTransaction(tt as any)}
|
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex row fluid css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}>
|
<Flex row fluid css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}>
|
||||||
<Text muted css={{ mr: "$3" }}>
|
<Text muted css={{ mr: "$3" }}>
|
||||||
Account:{" "}
|
From account:{" "}
|
||||||
</Text>
|
</Text>
|
||||||
<Select
|
<Select
|
||||||
instanceId="from-account"
|
instanceId="from-account"
|
||||||
placeholder="Select your account"
|
placeholder="Select account from which to send from"
|
||||||
css={{ width: "70%" }}
|
css={{ width: "70%" }}
|
||||||
options={accountOptions}
|
options={options}
|
||||||
value={selectedAccount}
|
/>
|
||||||
onChange={acc => setSelectedAccount(acc as any)}
|
</Flex>
|
||||||
|
<Flex row fluid css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}>
|
||||||
|
<Text muted css={{ mr: "$3" }}>
|
||||||
|
Amount (XRP):{" "}
|
||||||
|
</Text>
|
||||||
|
<Input defaultValue="0" variant="deep" css={{ width: "70%", flex: "inherit", height: "$9" }} />
|
||||||
|
</Flex>
|
||||||
|
<Flex row fluid css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}>
|
||||||
|
<Text muted css={{ mr: "$3" }}>
|
||||||
|
To account:{" "}
|
||||||
|
</Text>
|
||||||
|
<Select
|
||||||
|
instanceId="to-account"
|
||||||
|
placeholder="Select account from which to send from"
|
||||||
|
css={{ width: "70%" }}
|
||||||
|
options={options}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
{txFields.Amount !== undefined && (
|
|
||||||
<Flex row fluid css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}>
|
|
||||||
<Text muted css={{ mr: "$3" }}>
|
|
||||||
Amount (XRP):{" "}
|
|
||||||
</Text>
|
|
||||||
<Input
|
|
||||||
value={txFields.Amount.value}
|
|
||||||
onChange={e =>
|
|
||||||
setTxFields({
|
|
||||||
...txFields,
|
|
||||||
Amount: { type: "currency", value: e.target.value },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
variant="deep"
|
|
||||||
css={{ width: "70%", flex: "inherit", height: "$9" }}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
{txFields.Destination !== undefined && (
|
|
||||||
<Flex row fluid css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}>
|
|
||||||
<Text muted css={{ mr: "$3" }}>
|
|
||||||
Destination account:{" "}
|
|
||||||
</Text>
|
|
||||||
<Select
|
|
||||||
instanceId="to-account"
|
|
||||||
placeholder="Select the destination account"
|
|
||||||
css={{ width: "70%" }}
|
|
||||||
options={destAccountOptions}
|
|
||||||
value={selectedDestAccount}
|
|
||||||
isClearable
|
|
||||||
onChange={acc => setSelectedDestAccount(acc as any)}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
{otherFields.map(field => {
|
|
||||||
let _value = txFields[field];
|
|
||||||
let value = typeof _value === "object" ? _value.value : _value;
|
|
||||||
value = typeof value === "object" ? JSON.stringify(value) : value?.toLocaleString();
|
|
||||||
let isCurrency = typeof _value === "object" && _value.type === "currency";
|
|
||||||
return (
|
|
||||||
<Flex
|
|
||||||
key={field}
|
|
||||||
row
|
|
||||||
fluid
|
|
||||||
css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}
|
|
||||||
>
|
|
||||||
<Text muted css={{ mr: "$3" }}>
|
|
||||||
{field + (isCurrency ? " (XRP)" : "")}:{" "}
|
|
||||||
</Text>
|
|
||||||
<Input
|
|
||||||
value={value}
|
|
||||||
onChange={e =>
|
|
||||||
setTxFields({
|
|
||||||
...txFields,
|
|
||||||
[field]:
|
|
||||||
typeof _value === "object"
|
|
||||||
? { ..._value, value: e.target.value }
|
|
||||||
: e.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
variant="deep"
|
|
||||||
css={{ width: "70%", flex: "inherit", height: "$9" }}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Container>
|
</Container>
|
||||||
<Flex
|
<Flex row css={{ justifyContent: "space-between" }}>
|
||||||
row
|
|
||||||
css={{
|
|
||||||
justifyContent: "space-between",
|
|
||||||
position: "absolute",
|
|
||||||
left: 0,
|
|
||||||
bottom: 0,
|
|
||||||
width: "100%",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button outline>VIEW AS JSON</Button>
|
<Button outline>VIEW AS JSON</Button>
|
||||||
<Flex row>
|
<Flex row>
|
||||||
<Button onClick={resetState} outline css={{ mr: "$3" }}>
|
<Button outline css={{ mr: "$3" }}>
|
||||||
RESET
|
RESET
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button variant="primary">
|
||||||
variant="primary"
|
|
||||||
onClick={submitTest}
|
|
||||||
isLoading={txIsLoading}
|
|
||||||
disabled={txIsDisabled}
|
|
||||||
>
|
|
||||||
<Play weight="bold" size="16px" />
|
<Play weight="bold" size="16px" />
|
||||||
RUN TEST
|
RUN TEST
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Test = () => {
|
const Test = () => {
|
||||||
const snap = useSnapshot(state);
|
|
||||||
return (
|
return (
|
||||||
<Container css={{ py: "$3", px: 0 }}>
|
<Container css={{ py: "$3", px: 0 }}>
|
||||||
<Flex
|
<Flex row fluid css={{ justifyContent: 'center', mb: "$2", height: '40vh', minHeight: '300px', p: '$3 $2' }}>
|
||||||
row
|
<Box css={{ width: "55%", px: "$2" }}>
|
||||||
fluid
|
|
||||||
css={{ justifyContent: "center", mb: "$2", height: "40vh", minHeight: "300px", p: "$3 $2" }}
|
|
||||||
>
|
|
||||||
<Box css={{ width: "60%", px: "$2", maxWidth: "800px", height: "100%", overflow: "auto" }}>
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
{/* TODO Dynamic tabs */}
|
{/* TODO Dynamic tabs */}
|
||||||
<Tab header="test1.json">
|
<Tab header="test1.json">
|
||||||
@@ -302,21 +95,17 @@ const Test = () => {
|
|||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
</Box>
|
||||||
<Box css={{ width: "40%", mx: "$2", height: "100%", maxWidth: "750px" }}>
|
<Box css={{ width: "45%", mx: "$2", height: '100%' }}>
|
||||||
<Accounts card hideDeployBtn showHookStats />
|
<Accounts card hideDeployBtn showHookStats />
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex row fluid css={{ borderBottom: "1px solid $mauve8" }}>
|
<Flex row fluid css={{ borderBottom: "1px solid $mauve8" }}>
|
||||||
<Box css={{ width: "50%", borderRight: "1px solid $mauve8" }}>
|
<Box css={{ width: "50%", borderRight: "1px solid $mauve8" }}>
|
||||||
<LogBox
|
<LogBox title="From Log" logs={[]} clearLog={() => {}} />
|
||||||
title="Development Log"
|
|
||||||
logs={snap.transactionLogs}
|
|
||||||
clearLog={() => (state.transactionLogs = [])}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box css={{ width: "50%" }}>
|
<Box css={{ width: "50%" }}>
|
||||||
<DebugStream />
|
<LogBox title="To Log" logs={[]} clearLog={() => {}} />
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { saveFile } from "./saveFile";
|
|||||||
import { syncToGist } from "./syncToGist";
|
import { syncToGist } from "./syncToGist";
|
||||||
import { updateEditorSettings } from "./updateEditorSettings";
|
import { updateEditorSettings } from "./updateEditorSettings";
|
||||||
import { downloadAsZip } from "./downloadAsZip";
|
import { downloadAsZip } from "./downloadAsZip";
|
||||||
import { sendTransaction } from "./sendTransaction";
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
addFaucetAccount,
|
addFaucetAccount,
|
||||||
@@ -20,6 +19,5 @@ export {
|
|||||||
saveFile,
|
saveFile,
|
||||||
syncToGist,
|
syncToGist,
|
||||||
updateEditorSettings,
|
updateEditorSettings,
|
||||||
downloadAsZip,
|
downloadAsZip
|
||||||
sendTransaction
|
|
||||||
};
|
};
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import { derive, sign } from "xrpl-accountlib";
|
|
||||||
|
|
||||||
import state from '..'
|
|
||||||
import type { IAccount } from "..";
|
|
||||||
|
|
||||||
interface TransactionOptions {
|
|
||||||
TransactionType: string,
|
|
||||||
Account?: string,
|
|
||||||
Fee?: string,
|
|
||||||
Destination?: string
|
|
||||||
[index: string]: any
|
|
||||||
}
|
|
||||||
|
|
||||||
export const sendTransaction = async (account: IAccount, txOptions: TransactionOptions) => {
|
|
||||||
if (!state.client) throw Error('XRPL client not initalized')
|
|
||||||
|
|
||||||
const { Fee = "1000", ...opts } = txOptions
|
|
||||||
const tx: TransactionOptions = {
|
|
||||||
Account: account.address,
|
|
||||||
Sequence: account.sequence, // TODO auto-fillable
|
|
||||||
Fee, // TODO auto-fillable
|
|
||||||
...opts
|
|
||||||
};
|
|
||||||
console.log({ tx });
|
|
||||||
try {
|
|
||||||
const signedAccount = derive.familySeed(account.secret);
|
|
||||||
const { signedTransaction } = sign(tx, signedAccount);
|
|
||||||
const response = await state.client.send({
|
|
||||||
command: "submit",
|
|
||||||
tx_blob: signedTransaction,
|
|
||||||
});
|
|
||||||
if (response.engine_result === "tesSUCCESS") {
|
|
||||||
state.transactionLogs.push({
|
|
||||||
type: 'success',
|
|
||||||
message: `Transaction success [${response.engine_result}]: ${response.engine_result_message}`
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
state.transactionLogs.push({
|
|
||||||
type: "error",
|
|
||||||
message: `[${response.error || response.engine_result}] ${response.error_exception || response.engine_result_message}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
state.transactionLogs.push({
|
|
||||||
type: "error",
|
|
||||||
message: err instanceof Error ? `Error: ${err.message}` : 'Something went wrong, try again later',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -49,8 +49,6 @@ export interface IState {
|
|||||||
compiling: boolean;
|
compiling: boolean;
|
||||||
logs: ILog[];
|
logs: ILog[];
|
||||||
deployLogs: ILog[];
|
deployLogs: ILog[];
|
||||||
transactionLogs: ILog[];
|
|
||||||
debugLogs: ILog[];
|
|
||||||
editorCtx?: typeof monaco.editor;
|
editorCtx?: typeof monaco.editor;
|
||||||
editorSettings: {
|
editorSettings: {
|
||||||
tabSize: number;
|
tabSize: number;
|
||||||
@@ -72,8 +70,6 @@ let initialState: IState = {
|
|||||||
compiling: false,
|
compiling: false,
|
||||||
logs: [],
|
logs: [],
|
||||||
deployLogs: [],
|
deployLogs: [],
|
||||||
transactionLogs: [],
|
|
||||||
debugLogs: [],
|
|
||||||
editorCtx: undefined,
|
editorCtx: undefined,
|
||||||
gistId: undefined,
|
gistId: undefined,
|
||||||
gistOwner: undefined,
|
gistOwner: undefined,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// stitches.config.ts
|
// stitches.config.ts
|
||||||
import type Stitches from '@stitches/react';
|
import type Stitches from '@stitches/react';
|
||||||
import { createStitches } from '@stitches/react';
|
import { createStitches } from '@stitches/react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
gray,
|
gray,
|
||||||
blue,
|
blue,
|
||||||
@@ -48,12 +47,11 @@ export const {
|
|||||||
...yellow,
|
...yellow,
|
||||||
...purple,
|
...purple,
|
||||||
background: "$gray1",
|
background: "$gray1",
|
||||||
backgroundAlt: "$gray4",
|
|
||||||
text: "$gray12",
|
text: "$gray12",
|
||||||
primary: "$plum",
|
primary: "$plum",
|
||||||
white: "white",
|
white: "white",
|
||||||
black: "black",
|
black: "black",
|
||||||
'deep': 'rgb(244, 244, 244)'
|
'deep': 'rgb(248, 248, 248)'
|
||||||
},
|
},
|
||||||
fonts: {
|
fonts: {
|
||||||
body: 'Work Sans, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif',
|
body: 'Work Sans, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif',
|
||||||
@@ -306,8 +304,7 @@ export const darkTheme = createTheme('dark', {
|
|||||||
...pinkDark,
|
...pinkDark,
|
||||||
...yellowDark,
|
...yellowDark,
|
||||||
...purpleDark,
|
...purpleDark,
|
||||||
deep: 'rgb(10, 10, 10)',
|
deep: 'rgb(10, 10, 10)'
|
||||||
// backgroundA: transparentize(0.1, grayDark.gray1),
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user