Compare commits
165 Commits
transactio
...
feat/remov
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
650324f434 | ||
|
|
74db96e8a5 | ||
|
|
c99c821081 | ||
|
|
6e39b90c1e | ||
|
|
f186a807c1 | ||
|
|
5ad9ed1688 | ||
|
|
234832138f | ||
|
|
28d94a1475 | ||
|
|
594aee6cd2 | ||
|
|
d75910972f | ||
|
|
589c604a12 | ||
|
|
8394a11705 | ||
|
|
4ad329882c | ||
|
|
ee86b91e82 | ||
|
|
d2addf782e | ||
|
|
51f7bd509b | ||
|
|
e064251ff9 | ||
|
|
5aeed7c246 | ||
|
|
8d03edc299 | ||
|
|
95022ef121 | ||
|
|
4519906b78 | ||
|
|
88a47c49a4 | ||
|
|
1ab03f9bed | ||
|
|
84ff667135 | ||
|
|
0d10e782f3 | ||
|
|
84e6763495 | ||
|
|
7ffcfd694d | ||
|
|
77e4917d38 | ||
|
|
e4238a40cc | ||
|
|
42c0b20512 | ||
|
|
43154ff6d8 | ||
|
|
8197b510f9 | ||
|
|
fc7652f48e | ||
|
|
bd32555617 | ||
|
|
fc6f420e1e | ||
|
|
d3c36765de | ||
|
|
2628a12673 | ||
|
|
f6c1869b5d | ||
|
|
62c8b4f217 | ||
|
|
8798e5a233 | ||
|
|
5f7d42843c | ||
|
|
302b36dde8 | ||
|
|
3e7c7b1969 | ||
|
|
936bbc503a | ||
|
|
81890c8833 | ||
|
|
50fa20c39a | ||
|
|
11f2cffc87 | ||
|
|
bbd1d162f0 | ||
|
|
b301a860bf | ||
|
|
ff697b96ea | ||
|
|
48e9898e31 | ||
|
|
2e25242ebe | ||
|
|
e32e07f7fd | ||
|
|
0d2a17008e | ||
|
|
a87b3de6c4 | ||
|
|
23068ff477 | ||
|
|
a12a5dfbac | ||
|
|
5a598cb091 | ||
|
|
be39054a2f | ||
|
|
0add65dd1c | ||
|
|
82170ad4f8 | ||
|
|
af49426eb0 | ||
|
|
48a86e3386 | ||
|
|
c82c35b5a1 | ||
|
|
f849be1f80 | ||
|
|
694d07fa0e | ||
|
|
b9aa3e2adc | ||
|
|
5b573b2379 | ||
|
|
23538b1502 | ||
|
|
723602ebdc | ||
|
|
f8fdeaf9ce | ||
|
|
e75b971718 | ||
|
|
11a35a5932 | ||
|
|
611f875761 | ||
|
|
a7df50c194 | ||
|
|
0c6c60ed29 | ||
|
|
e82662647f | ||
|
|
5490e7205a | ||
|
|
d8e218392a | ||
|
|
723722df58 | ||
|
|
2ff85ede06 | ||
|
|
052a1e5b60 | ||
|
|
7f8f47cb14 | ||
|
|
ddb043c104 | ||
|
|
d2ad6537d7 | ||
|
|
8f004ee4da | ||
|
|
b90bf67c20 | ||
|
|
746112e637 | ||
|
|
13bfd42093 | ||
|
|
c1f7d7d51c | ||
|
|
38a097a8f9 | ||
|
|
db0ffe999e | ||
|
|
6d88c4e546 | ||
|
|
09f58f18ae | ||
|
|
e11ddaffb0 | ||
|
|
2e88f568b8 | ||
|
|
237d504f17 | ||
|
|
197fc09e1d | ||
|
|
2c74a93aee | ||
|
|
5209644780 | ||
|
|
ed37427da8 | ||
|
|
daee9de96c | ||
|
|
e4b10d12c2 | ||
|
|
64eabb4502 | ||
|
|
12a24d3d86 | ||
|
|
ce91182c7b | ||
|
|
2e3a0e557e | ||
|
|
395e02343b | ||
|
|
3682dd4946 | ||
|
|
3070ed706e | ||
|
|
4b73687779 | ||
|
|
6b9a9ef978 | ||
|
|
bc5bb5be39 | ||
|
|
0fe83811b9 | ||
|
|
c6359aa853 | ||
|
|
c9c818c8f3 | ||
|
|
c521246393 | ||
|
|
8936b34361 | ||
|
|
5993d2762f | ||
|
|
0a44b5b5d1 | ||
|
|
810d3b2524 | ||
|
|
a3393ded1e | ||
|
|
17ede265b1 | ||
|
|
629070edad | ||
|
|
cc83924c27 | ||
|
|
e3e964f72a | ||
|
|
0def1d30a6 | ||
|
|
bdb2c0cf8f | ||
|
|
3e8dbc9793 | ||
|
|
d735cd3833 | ||
|
|
eddf228283 | ||
|
|
f9d617efdc | ||
|
|
43796021da | ||
|
|
241c21782d | ||
|
|
1a3f5d144c | ||
|
|
66fb68d52e | ||
|
|
aaeb32a576 | ||
|
|
78b5dcceb6 | ||
|
|
ce81c11c29 | ||
|
|
3a98b95e3d | ||
|
|
8bc36655e2 | ||
|
|
b6ab536a60 | ||
|
|
37a3d2b207 | ||
|
|
cd0c5f8a0d | ||
|
|
8dde89fa9a | ||
|
|
ca3d60cfb8 | ||
|
|
1a4d53cfbc | ||
|
|
94e126782b | ||
|
|
cc03c64f0a | ||
|
|
3647aa6274 | ||
|
|
a2a58f0ba9 | ||
|
|
c544a03be4 | ||
|
|
9a09da88ec | ||
|
|
5850551906 | ||
|
|
e35e520d24 | ||
|
|
8077fc5865 | ||
|
|
bff01b4a9f | ||
|
|
de5380d6f3 | ||
|
|
eda2a9596a | ||
|
|
4f042ef3b7 | ||
|
|
17c67822a9 | ||
|
|
e6f613ae0b | ||
|
|
9b822cfda4 | ||
|
|
739918647d | ||
|
|
1f334d6253 |
@@ -2,4 +2,9 @@ NEXTAUTH_URL=https://example.com
|
||||
GITHUB_SECRET=""
|
||||
GITHUB_ID=""
|
||||
NEXT_PUBLIC_COMPILE_API_ENDPOINT="http://localhost:9000/api/build"
|
||||
NEXT_PUBLIC_LANGUAGE_SERVER_API_ENDPOINT="ws://localhost:9000/language-server/c"
|
||||
NEXT_PUBLIC_COMPILE_API_BASE_URL="http://localhost:9000"
|
||||
NEXT_PUBLIC_LANGUAGE_SERVER_API_ENDPOINT="ws://localhost:9000/language-server/c"
|
||||
NEXT_PUBLIC_TESTNET_URL="hooks-testnet-v2.xrpl-labs.com"
|
||||
NEXT_PUBLIC_DEBUG_STREAM_URL="hooks-testnet-v2-debugstream.xrpl-labs.com"
|
||||
NEXT_PUBLIC_EXPLORER_URL="hooks-testnet-v2-explorer.xrpl-labs.com"
|
||||
NEXT_PUBLIC_SITE_URL=http://localhost:3000
|
||||
1
.prettierignore
Normal file
@@ -0,0 +1 @@
|
||||
*.md
|
||||
@@ -87,6 +87,9 @@ By default `@monaco-editor/react` was using 0.29.? version of Monaco editor. @co
|
||||
|
||||
Monaco Languageclient related stuff is found from `./utils/languageClient.ts`. Basically we're connecting the editor to clangd language server which lives on separate backend. That project can be found from https://github.com/eqlabs/xrpl-hooks-compiler/. If you need access to that project ask permissions from @vbar (Vaclav Barta) on GitHub.
|
||||
|
||||
### Language server hover messages
|
||||
If you want to extend hover messages provided by language-server you can add extra docs to `xrpl-hooks-docs/md/` folder. Just make sure the filename is matching with the error code that comes from language server. So lets say you want to add extra documentation for `hooks-func-addr-taken` check create new file called `hooks-func-addr-taken.md` and then remember to import and export it on `docs.ts` file with same logic as the other files.
|
||||
|
||||
## Global state management
|
||||
|
||||
Global state management is handled with library called Valtio (https://github.com/pmndrs/valtio). Initial state can be found from `./state/index.ts` file. All the actions which updates the state is found under `./state/actions/` folder.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import toast from "react-hot-toast";
|
||||
import { useSnapshot } from "valtio";
|
||||
import { ArrowSquareOut, Copy, Wallet, X } from "phosphor-react";
|
||||
import { ArrowSquareOut, Copy, Trash, Wallet, X } from "phosphor-react";
|
||||
import React, { useEffect, useState, FC } from "react";
|
||||
import Dinero from "dinero.js";
|
||||
|
||||
import Button from "./Button";
|
||||
import { addFaucetAccount, deployHook, importAccount } from "../state/actions";
|
||||
import { addFaucetAccount, importAccount } from "../state/actions";
|
||||
import state from "../state";
|
||||
import Box from "./Box";
|
||||
import { Container, Heading, Stack, Text, Flex } from ".";
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
} from "./Dialog";
|
||||
import { css } from "../stitches.config";
|
||||
import { Input } from "./Input";
|
||||
import truncate from "../utils/truncate";
|
||||
|
||||
const labelStyle = css({
|
||||
color: "$mauve10",
|
||||
@@ -26,8 +27,12 @@ const labelStyle = css({
|
||||
fontSize: "10px",
|
||||
mb: "$0.5",
|
||||
});
|
||||
import transactionsData from "../content/transactions.json";
|
||||
import { SetHookDialog } from "./SetHookDialog";
|
||||
import { addFunds } from "../state/actions/addFaucetAccount";
|
||||
import { deleteHook } from "../state/actions/deployHook";
|
||||
|
||||
const AccountDialog = ({
|
||||
export const AccountDialog = ({
|
||||
activeAccountAddress,
|
||||
setActiveAccountAddress,
|
||||
}: {
|
||||
@@ -36,11 +41,13 @@ const AccountDialog = ({
|
||||
}) => {
|
||||
const snap = useSnapshot(state);
|
||||
const [showSecret, setShowSecret] = useState(false);
|
||||
const activeAccount = snap.accounts.find(account => account.address === activeAccountAddress);
|
||||
const activeAccount = snap.accounts.find(
|
||||
(account) => account.address === activeAccountAddress
|
||||
);
|
||||
return (
|
||||
<Dialog
|
||||
open={Boolean(activeAccountAddress)}
|
||||
onOpenChange={open => {
|
||||
onOpenChange={(open) => {
|
||||
setShowSecret(false);
|
||||
!open && setActiveAccountAddress(null);
|
||||
}}
|
||||
@@ -84,6 +91,22 @@ const AccountDialog = ({
|
||||
}}
|
||||
>
|
||||
<Wallet size="15px" /> {activeAccount?.name}
|
||||
<DialogClose asChild>
|
||||
<Button
|
||||
size="xs"
|
||||
outline
|
||||
css={{ ml: "auto", mr: "$9" }}
|
||||
tabIndex={-1}
|
||||
onClick={() => {
|
||||
const index = state.accounts.findIndex(
|
||||
(acc) => acc.address === activeAccount?.address
|
||||
);
|
||||
state.accounts.splice(index, 1);
|
||||
}}
|
||||
>
|
||||
Delete Account <Trash size="15px" />
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</DialogTitle>
|
||||
<DialogDescription as="div" css={{ fontFamily: "$monospace" }}>
|
||||
<Stack css={{ display: "flex", flexDirection: "column", gap: "$3" }}>
|
||||
@@ -135,7 +158,7 @@ const AccountDialog = ({
|
||||
}}
|
||||
ghost
|
||||
size="xs"
|
||||
onClick={() => setShowSecret(curr => !curr)}
|
||||
onClick={() => setShowSecret((curr) => !curr)}
|
||||
>
|
||||
{showSecret ? "Hide" : "Show"}
|
||||
</Button>
|
||||
@@ -161,6 +184,8 @@ const AccountDialog = ({
|
||||
<Text
|
||||
css={{
|
||||
fontFamily: "$monospace",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{Dinero({
|
||||
@@ -173,15 +198,34 @@ const AccountDialog = ({
|
||||
currency: "XRP",
|
||||
currencyDisplay: "name",
|
||||
})}
|
||||
<Button
|
||||
css={{
|
||||
fontFamily: "$monospace",
|
||||
lineHeight: 2,
|
||||
mt: "2px",
|
||||
ml: "$3",
|
||||
}}
|
||||
ghost
|
||||
size="xs"
|
||||
onClick={() => {
|
||||
addFunds(activeAccount?.address || "");
|
||||
}}
|
||||
>
|
||||
Add Funds
|
||||
</Button>
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex css={{ marginLeft: "auto" }}>
|
||||
<a
|
||||
href={`https://hooks-testnet-explorer.xrpl-labs.com/${activeAccount?.address}`}
|
||||
href={`https://${process.env.NEXT_PUBLIC_EXPLORER_URL}/${activeAccount?.address}`}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<Button size="sm" ghost css={{ color: "$green11 !important", mt: "$3" }}>
|
||||
<Button
|
||||
size="sm"
|
||||
ghost
|
||||
css={{ color: "$grass11 !important", mt: "$3" }}
|
||||
>
|
||||
<ArrowSquareOut size="15px" />
|
||||
</Button>
|
||||
</a>
|
||||
@@ -195,15 +239,26 @@ const AccountDialog = ({
|
||||
fontFamily: "$monospace",
|
||||
}}
|
||||
>
|
||||
{activeAccount && activeAccount?.hooks?.length > 0
|
||||
? activeAccount?.hooks
|
||||
.map(i => {
|
||||
return `${i?.substring(0, 6)}...${i?.substring(i.length - 4)}`;
|
||||
})
|
||||
.join(", ")
|
||||
{activeAccount && activeAccount.hooks.length > 0
|
||||
? activeAccount.hooks.map((i) => truncate(i, 12)).join(",")
|
||||
: "–"}
|
||||
</Text>
|
||||
</Flex>
|
||||
{activeAccount && activeAccount?.hooks?.length > 0 && (
|
||||
<Flex css={{ marginLeft: "auto" }}>
|
||||
<Button
|
||||
size="xs"
|
||||
outline
|
||||
disabled={activeAccount.isLoading}
|
||||
css={{ mt: "$3", mr: "$1", ml: "auto" }}
|
||||
onClick={() => {
|
||||
deleteHook(activeAccount);
|
||||
}}
|
||||
>
|
||||
Delete Hook <Trash size="15px" />
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</Stack>
|
||||
</DialogDescription>
|
||||
@@ -223,15 +278,17 @@ interface AccountProps {
|
||||
showHookStats?: boolean;
|
||||
}
|
||||
|
||||
const Accounts: FC<AccountProps> = props => {
|
||||
const Accounts: FC<AccountProps> = (props) => {
|
||||
const snap = useSnapshot(state);
|
||||
const [activeAccountAddress, setActiveAccountAddress] = useState<string | null>(null);
|
||||
const [activeAccountAddress, setActiveAccountAddress] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
useEffect(() => {
|
||||
const fetchAccInfo = async () => {
|
||||
if (snap.clientStatus === "online") {
|
||||
const requests = snap.accounts.map(acc =>
|
||||
const requests = snap.accounts.map((acc) =>
|
||||
snap.client?.send({
|
||||
id: acc.address,
|
||||
id: `hooks-builder-req-info-${acc.address}`,
|
||||
command: "account_info",
|
||||
account: acc.address,
|
||||
})
|
||||
@@ -241,15 +298,17 @@ const Accounts: FC<AccountProps> = props => {
|
||||
const address = res?.account_data?.Account as string;
|
||||
const balance = res?.account_data?.Balance as string;
|
||||
const sequence = res?.account_data?.Sequence as number;
|
||||
const accountToUpdate = state.accounts.find(acc => acc.address === address);
|
||||
const accountToUpdate = state.accounts.find(
|
||||
(acc) => acc.address === address
|
||||
);
|
||||
if (accountToUpdate) {
|
||||
accountToUpdate.xrp = balance;
|
||||
accountToUpdate.sequence = sequence;
|
||||
}
|
||||
});
|
||||
const objectRequests = snap.accounts.map(acc => {
|
||||
const objectRequests = snap.accounts.map((acc) => {
|
||||
return snap.client?.send({
|
||||
id: `${acc.address}-hooks`,
|
||||
id: `hooks-builder-req-objects-${acc.address}`,
|
||||
command: "account_objects",
|
||||
account: acc.address,
|
||||
});
|
||||
@@ -257,11 +316,14 @@ const Accounts: FC<AccountProps> = props => {
|
||||
const objectResponses = await Promise.all(objectRequests);
|
||||
objectResponses.forEach((res: any) => {
|
||||
const address = res?.account as string;
|
||||
const accountToUpdate = state.accounts.find(acc => acc.address === address);
|
||||
const accountToUpdate = state.accounts.find(
|
||||
(acc) => acc.address === address
|
||||
);
|
||||
if (accountToUpdate) {
|
||||
accountToUpdate.hooks = res.account_objects
|
||||
.filter((ac: any) => ac?.LedgerEntryType === "Hook")
|
||||
.map((oo: any) => oo.HookHash);
|
||||
accountToUpdate.hooks =
|
||||
res.account_objects
|
||||
.find((ac: any) => ac?.LedgerEntryType === "Hook")
|
||||
?.Hooks?.map((oo: any) => oo.Hook.HookHash) || [];
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -270,7 +332,7 @@ const Accounts: FC<AccountProps> = props => {
|
||||
let fetchAccountInfoInterval: NodeJS.Timer;
|
||||
if (snap.clientStatus === "online") {
|
||||
fetchAccInfo();
|
||||
fetchAccountInfoInterval = setInterval(() => fetchAccInfo(), 2000);
|
||||
fetchAccountInfoInterval = setInterval(() => fetchAccInfo(), 10000);
|
||||
}
|
||||
|
||||
return () => {
|
||||
@@ -289,18 +351,19 @@ const Accounts: FC<AccountProps> = props => {
|
||||
display: "flex",
|
||||
backgroundColor: props.card ? "$deep" : "$mauve1",
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
flex: "1",
|
||||
height: "100%",
|
||||
flexShrink: 0,
|
||||
borderTop: "1px solid $mauve6",
|
||||
borderRight: "1px solid $mauve6",
|
||||
borderLeft: "1px solid $mauve6",
|
||||
borderBottom: "1px solid $mauve6",
|
||||
border: "1px solid $mauve6",
|
||||
borderRadius: props.card ? "$md" : undefined,
|
||||
}}
|
||||
>
|
||||
<Container css={{ p: 0, flexShrink: 1, height: "100%" }}>
|
||||
<Flex css={{ py: "$3", borderBottom: props.card ? "1px solid $mauve6" : undefined }}>
|
||||
<Flex
|
||||
css={{
|
||||
py: "$3",
|
||||
borderBottom: props.card ? "1px solid $mauve6" : undefined,
|
||||
}}
|
||||
>
|
||||
<Heading
|
||||
as="h3"
|
||||
css={{
|
||||
@@ -331,14 +394,13 @@ const Accounts: FC<AccountProps> = props => {
|
||||
fontSize: "13px",
|
||||
wordWrap: "break-word",
|
||||
fontWeight: "$body",
|
||||
fontFamily: "$monospace",
|
||||
gap: 0,
|
||||
height: "calc(100% - 52px)",
|
||||
flexWrap: "nowrap",
|
||||
overflowY: "auto",
|
||||
}}
|
||||
>
|
||||
{snap.accounts.map(account => (
|
||||
{snap.accounts.map((account) => (
|
||||
<Flex
|
||||
column
|
||||
key={account.address + account.name}
|
||||
@@ -350,7 +412,7 @@ const Accounts: FC<AccountProps> = props => {
|
||||
borderBottom: props.card ? "1px solid $mauve6" : undefined,
|
||||
"@hover": {
|
||||
"&:hover": {
|
||||
background: "$mauve3",
|
||||
background: "$backgroundAlt",
|
||||
},
|
||||
},
|
||||
}}
|
||||
@@ -363,7 +425,12 @@ const Accounts: FC<AccountProps> = props => {
|
||||
>
|
||||
<Box>
|
||||
<Text>{account.name} </Text>
|
||||
<Text css={{ color: "$mauve9" }}>
|
||||
<Text
|
||||
css={{
|
||||
color: "$textMuted",
|
||||
wordBreak: "break-word",
|
||||
}}
|
||||
>
|
||||
{account.address} (
|
||||
{Dinero({
|
||||
amount: Number(account?.xrp || "0"),
|
||||
@@ -379,28 +446,20 @@ const Accounts: FC<AccountProps> = props => {
|
||||
</Text>
|
||||
</Box>
|
||||
{!props.hideDeployBtn && (
|
||||
<Button
|
||||
css={{ ml: "auto" }}
|
||||
size="xs"
|
||||
uppercase
|
||||
isLoading={account.isLoading}
|
||||
disabled={
|
||||
account.isLoading ||
|
||||
!snap.files.filter(file => file.compiledWatContent).length
|
||||
}
|
||||
variant="secondary"
|
||||
onClick={e => {
|
||||
<div
|
||||
className="hook-deploy-button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
deployHook(account);
|
||||
}}
|
||||
>
|
||||
Deploy
|
||||
</Button>
|
||||
<SetHookDialog account={account} />
|
||||
</div>
|
||||
)}
|
||||
</Flex>
|
||||
{props.showHookStats && (
|
||||
<Text muted small css={{ mt: "$2" }}>
|
||||
X hooks installed
|
||||
{account.hooks.length} hook
|
||||
{account.hooks.length === 1 ? "" : "s"} installed
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
@@ -415,6 +474,11 @@ const Accounts: FC<AccountProps> = props => {
|
||||
);
|
||||
};
|
||||
|
||||
export const transactionsOptions = transactionsData.map((tx) => ({
|
||||
value: tx.TransactionType,
|
||||
label: tx.TransactionType,
|
||||
}));
|
||||
|
||||
const ImportAccountDialog = () => {
|
||||
const [value, setValue] = useState("");
|
||||
return (
|
||||
@@ -432,7 +496,7 @@ const ImportAccountDialog = () => {
|
||||
name="secret"
|
||||
type="password"
|
||||
value={value}
|
||||
onChange={e => setValue(e.target.value)}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
/>
|
||||
</DialogDescription>
|
||||
|
||||
|
||||
@@ -66,6 +66,12 @@ export const StyledButton = styled("button", {
|
||||
},
|
||||
},
|
||||
variant: {
|
||||
link: {
|
||||
textDecoration: "underline",
|
||||
fontSize: "inherit",
|
||||
color: "$textMuted",
|
||||
textUnderlineOffset: "2px",
|
||||
},
|
||||
default: {
|
||||
backgroundColor: "$mauve12",
|
||||
boxShadow: "inset 0 0 0 1px $colors$mauve12",
|
||||
@@ -91,26 +97,26 @@ export const StyledButton = styled("button", {
|
||||
},
|
||||
},
|
||||
primary: {
|
||||
backgroundColor: `$pink9`,
|
||||
boxShadow: "inset 0 0 0 1px $colors$pink9",
|
||||
backgroundColor: `$accent`,
|
||||
boxShadow: "inset 0 0 0 1px $colors$purple9",
|
||||
color: "$white",
|
||||
"@hover": {
|
||||
"&:hover": {
|
||||
backgroundColor: "$pink10",
|
||||
boxShadow: "inset 0 0 0 1px $colors$pink11",
|
||||
backgroundColor: "$purple10",
|
||||
boxShadow: "inset 0 0 0 1px $colors$purple11",
|
||||
},
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: "$pink8",
|
||||
boxShadow: "inset 0 0 0 1px $colors$pink8",
|
||||
backgroundColor: "$purple8",
|
||||
boxShadow: "inset 0 0 0 1px $colors$purple8",
|
||||
},
|
||||
"&:focus": {
|
||||
boxShadow: "inset 0 0 0 2px $colors$pink12",
|
||||
boxShadow: "inset 0 0 0 2px $colors$purple12",
|
||||
},
|
||||
'&[data-radix-popover-trigger][data-state="open"], &[data-radix-dropdown-menu-trigger][data-state="open"]':
|
||||
{
|
||||
backgroundColor: "$mauve4",
|
||||
boxShadow: "inset 0 0 0 1px $colors$pink8",
|
||||
boxShadow: "inset 0 0 0 1px $colors$purple8",
|
||||
},
|
||||
},
|
||||
secondary: {
|
||||
@@ -136,8 +142,45 @@ export const StyledButton = styled("button", {
|
||||
boxShadow: "inset 0 0 0 1px $colors$purple8",
|
||||
},
|
||||
},
|
||||
destroy: {
|
||||
backgroundColor: `$red9`,
|
||||
boxShadow: "inset 0 0 0 1px $colors$red9",
|
||||
color: "$white",
|
||||
"@hover": {
|
||||
"&:hover": {
|
||||
backgroundColor: "$red10",
|
||||
boxShadow: "inset 0 0 0 1px $colors$red11",
|
||||
},
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: "$red8",
|
||||
boxShadow: "inset 0 0 0 1px $colors$red8",
|
||||
},
|
||||
"&:focus": {
|
||||
boxShadow: "inset 0 0 0 2px $colors$red12",
|
||||
},
|
||||
'&[data-radix-popover-trigger][data-state="open"], &[data-radix-dropdown-menu-trigger][data-state="open"]':
|
||||
{
|
||||
backgroundColor: "$mauve4",
|
||||
boxShadow: "inset 0 0 0 1px $colors$red8",
|
||||
},
|
||||
},
|
||||
},
|
||||
muted: {
|
||||
true: {
|
||||
color: "$textMuted",
|
||||
},
|
||||
},
|
||||
isDisabled: {
|
||||
true: {
|
||||
opacity: 0.6,
|
||||
// pointerEvents: "none",
|
||||
cursor: "auto",
|
||||
"&:hover": {
|
||||
boxShadow: "inherit",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
outline: {
|
||||
true: {
|
||||
backgroundColor: "transparent",
|
||||
|
||||
147
components/DebugStream.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { proxy, ref, useSnapshot } from "valtio";
|
||||
import { Select } from ".";
|
||||
import state, { ILog } from "../state";
|
||||
import { extractJSON } from "../utils/json";
|
||||
import LogBox from "./LogBox";
|
||||
|
||||
interface ISelect<T = string> {
|
||||
label: string;
|
||||
value: T;
|
||||
}
|
||||
|
||||
const streamState = proxy({
|
||||
selectedAccount: null as ISelect | null,
|
||||
logs: [] as ILog[],
|
||||
socket: undefined as WebSocket | undefined,
|
||||
});
|
||||
|
||||
const DebugStream = () => {
|
||||
const { selectedAccount, logs, socket } = useSnapshot(streamState);
|
||||
const { accounts } = useSnapshot(state);
|
||||
|
||||
const accountOptions = accounts.map((acc) => ({
|
||||
label: acc.name,
|
||||
value: acc.address,
|
||||
}));
|
||||
|
||||
const renderNav = () => (
|
||||
<>
|
||||
<Select
|
||||
instanceId="DSAccount"
|
||||
placeholder="Select account"
|
||||
options={accountOptions}
|
||||
hideSelectedOptions
|
||||
value={selectedAccount}
|
||||
onChange={(acc) => (streamState.selectedAccount = acc as any)}
|
||||
css={{ width: "100%" }}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
const prepareLog = useCallback((str: any): ILog => {
|
||||
if (typeof str !== "string") throw Error("Unrecognized debug log stream!");
|
||||
|
||||
const match = str.match(/([\s\S]+(?:UTC|ISO|GMT[+|-]\d+))\ ?([\s\S]*)/m);
|
||||
const [_, tm, msg] = match || [];
|
||||
|
||||
const extracted = extractJSON(msg);
|
||||
const timestamp = isNaN(Date.parse(tm || ""))
|
||||
? tm
|
||||
: new Date(tm).toLocaleTimeString();
|
||||
|
||||
const message = !extracted
|
||||
? msg
|
||||
: msg.slice(0, extracted.start) + msg.slice(extracted.end + 1);
|
||||
|
||||
const jsonData = extracted
|
||||
? JSON.stringify(extracted.result, null, 2)
|
||||
: undefined;
|
||||
return {
|
||||
type: "log",
|
||||
message,
|
||||
timestamp,
|
||||
jsonData,
|
||||
defaultCollapsed: true,
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const account = selectedAccount?.value;
|
||||
if (account && (!socket || !socket.url.endsWith(account))) {
|
||||
socket?.close();
|
||||
streamState.socket = ref(
|
||||
new WebSocket(
|
||||
`wss://${process.env.NEXT_PUBLIC_DEBUG_STREAM_URL}/${account}`
|
||||
)
|
||||
);
|
||||
} else if (!account && socket) {
|
||||
socket.close();
|
||||
streamState.socket = undefined;
|
||||
}
|
||||
}, [selectedAccount?.value, socket]);
|
||||
|
||||
useEffect(() => {
|
||||
const account = selectedAccount?.value;
|
||||
const socket = streamState.socket;
|
||||
if (!socket) return;
|
||||
|
||||
const onOpen = () => {
|
||||
streamState.logs = [];
|
||||
streamState.logs.push({
|
||||
type: "success",
|
||||
message: `Debug stream opened for account ${account}`,
|
||||
});
|
||||
};
|
||||
const onError = () => {
|
||||
streamState.logs.push({
|
||||
type: "error",
|
||||
message: "Something went wrong! Check your connection and try again.",
|
||||
});
|
||||
};
|
||||
const onClose = (e: CloseEvent) => {
|
||||
streamState.logs.push({
|
||||
type: "error",
|
||||
message: `Connection was closed. [code: ${e.code}]`,
|
||||
});
|
||||
streamState.selectedAccount = null;
|
||||
};
|
||||
const onMessage = (event: any) => {
|
||||
if (!event.data) return;
|
||||
const log = prepareLog(event.data);
|
||||
// Filter out account_info and account_objects requests
|
||||
try {
|
||||
const parsed = JSON.parse(log.jsonData);
|
||||
if (parsed?.id?._Request?.includes("hooks-builder-req")) {
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
// Lets just skip if we cannot parse the message
|
||||
}
|
||||
return streamState.logs.push(log);
|
||||
};
|
||||
|
||||
socket.addEventListener("open", onOpen);
|
||||
socket.addEventListener("close", onClose);
|
||||
socket.addEventListener("error", onError);
|
||||
socket.addEventListener("message", onMessage);
|
||||
|
||||
return () => {
|
||||
socket.removeEventListener("open", onOpen);
|
||||
socket.removeEventListener("close", onClose);
|
||||
socket.removeEventListener("message", onMessage);
|
||||
socket.removeEventListener("error", onError);
|
||||
};
|
||||
}, [prepareLog, selectedAccount?.value, socket]);
|
||||
return (
|
||||
<LogBox
|
||||
enhanced
|
||||
renderNav={renderNav}
|
||||
title="Debug stream"
|
||||
logs={logs}
|
||||
clearLog={() => (streamState.logs = [])}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default DebugStream;
|
||||
@@ -1,20 +1,22 @@
|
||||
import React, { useRef } from "react";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { useSnapshot, ref } from "valtio";
|
||||
import Editor, { loader } from "@monaco-editor/react";
|
||||
import type monaco from "monaco-editor";
|
||||
import { useTheme } from "next-themes";
|
||||
import { useRouter } from "next/router";
|
||||
import NextLink from "next/link";
|
||||
import ReactTimeAgo from "react-time-ago";
|
||||
import filesize from "filesize";
|
||||
|
||||
import Box from "./Box";
|
||||
import Container from "./Container";
|
||||
import dark from "../theme/editor/amy.json";
|
||||
import light from "../theme/editor/xcode_default.json";
|
||||
import state from "../state";
|
||||
import wat from "../utils/wat-highlight";
|
||||
|
||||
import EditorNavigation from "./EditorNavigation";
|
||||
import Text from "./Text";
|
||||
import Link from "./Link";
|
||||
import { Button, Text, Link, Flex } from ".";
|
||||
|
||||
loader.config({
|
||||
paths: {
|
||||
@@ -22,11 +24,72 @@ loader.config({
|
||||
},
|
||||
});
|
||||
|
||||
const FILESIZE_BREAKPOINTS: [number, number] = [2 * 1024, 5 * 1024];
|
||||
|
||||
const DeployEditor = () => {
|
||||
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>();
|
||||
const snap = useSnapshot(state);
|
||||
const router = useRouter();
|
||||
const { theme } = useTheme();
|
||||
|
||||
const [showContent, setShowContent] = useState(false);
|
||||
|
||||
const activeFile = snap.files[snap.active];
|
||||
const compiledSize = activeFile?.compiledContent?.byteLength || 0;
|
||||
const color =
|
||||
compiledSize > FILESIZE_BREAKPOINTS[1]
|
||||
? "$error"
|
||||
: compiledSize > FILESIZE_BREAKPOINTS[0]
|
||||
? "$warning"
|
||||
: "$success";
|
||||
|
||||
const CompiledStatView = activeFile && (
|
||||
<Flex
|
||||
column
|
||||
align="center"
|
||||
css={{
|
||||
fontSize: "$sm",
|
||||
fontFamily: "$monospace",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
<Flex row align="center">
|
||||
<Text css={{ mr: "$1" }}>
|
||||
Compiled {activeFile.name.split(".")[0] + ".wasm"}
|
||||
</Text>
|
||||
{activeFile?.lastCompiled && (
|
||||
<ReactTimeAgo date={activeFile.lastCompiled} locale="en-US" />
|
||||
)}
|
||||
{activeFile.compiledContent?.byteLength && (
|
||||
<Text css={{ ml: "$2", color }}>
|
||||
({filesize(activeFile.compiledContent.byteLength)})
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
<Button variant="link" onClick={() => setShowContent(true)}>
|
||||
View as WAT-file
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
const NoContentView = !snap.loading && router.isReady && (
|
||||
<Text
|
||||
css={{
|
||||
mt: "-60px",
|
||||
fontSize: "$sm",
|
||||
fontFamily: "$monospace",
|
||||
maxWidth: "300px",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
{`You haven't compiled any files yet, compile files on `}
|
||||
<NextLink shallow href={`/develop/${router.query.slug}`} passHref>
|
||||
<Link as="a">develop view</Link>
|
||||
</NextLink>
|
||||
</Text>
|
||||
);
|
||||
const isContent =
|
||||
snap.files?.filter((file) => file.compiledWatContent).length > 0 &&
|
||||
router.isReady;
|
||||
return (
|
||||
<Box
|
||||
css={{
|
||||
@@ -34,65 +97,53 @@ const DeployEditor = () => {
|
||||
display: "flex",
|
||||
position: "relative",
|
||||
flexDirection: "column",
|
||||
backgroundColor: "$mauve3",
|
||||
backgroundColor: "$mauve2",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<EditorNavigation showWat />
|
||||
{snap.files?.filter((file) => file.compiledWatContent).length > 0 &&
|
||||
router.isReady ? (
|
||||
<Editor
|
||||
className="hooks-editor"
|
||||
// keepCurrentModel
|
||||
defaultLanguage={snap.files?.[snap.active]?.language}
|
||||
language={snap.files?.[snap.active]?.language}
|
||||
path={`file://tmp/c/${snap.files?.[snap.active]?.name}.wat`}
|
||||
value={snap.files?.[snap.active]?.compiledWatContent || ""}
|
||||
beforeMount={(monaco) => {
|
||||
if (!state.editorCtx) {
|
||||
state.editorCtx = ref(monaco.editor);
|
||||
// @ts-expect-error
|
||||
monaco.editor.defineTheme("dark", dark);
|
||||
// @ts-expect-error
|
||||
monaco.editor.defineTheme("light", light);
|
||||
}
|
||||
}}
|
||||
onMount={(editor, monaco) => {
|
||||
editorRef.current = editor;
|
||||
editor.updateOptions({
|
||||
glyphMargin: true,
|
||||
readOnly: true,
|
||||
});
|
||||
}}
|
||||
theme={theme === "dark" ? "dark" : "light"}
|
||||
/>
|
||||
) : (
|
||||
<Container
|
||||
css={{
|
||||
display: "flex",
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{!snap.loading && router.isReady && (
|
||||
<Text
|
||||
css={{
|
||||
mt: "-60px",
|
||||
fontSize: "14px",
|
||||
fontFamily: "$monospace",
|
||||
maxWidth: "300px",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
{`You haven't compiled any files yet, compile files on `}
|
||||
<NextLink shallow href={`/develop/${router.query.slug}`} passHref>
|
||||
<Link as="a">develop view</Link>
|
||||
</NextLink>
|
||||
</Text>
|
||||
)}
|
||||
</Container>
|
||||
)}
|
||||
<Container
|
||||
css={{
|
||||
display: "flex",
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{!isContent ? (
|
||||
NoContentView
|
||||
) : !showContent ? (
|
||||
CompiledStatView
|
||||
) : (
|
||||
<Editor
|
||||
className="hooks-editor"
|
||||
defaultLanguage={"wat"}
|
||||
language={"wat"}
|
||||
path={`file://tmp/c/${snap.files?.[snap.active]?.name}.wat`}
|
||||
value={snap.files?.[snap.active]?.compiledWatContent || ""}
|
||||
beforeMount={(monaco) => {
|
||||
monaco.languages.register({ id: "wat" });
|
||||
monaco.languages.setLanguageConfiguration("wat", wat.config);
|
||||
monaco.languages.setMonarchTokensProvider("wat", wat.tokens);
|
||||
if (!state.editorCtx) {
|
||||
state.editorCtx = ref(monaco.editor);
|
||||
// @ts-expect-error
|
||||
monaco.editor.defineTheme("dark", dark);
|
||||
// @ts-expect-error
|
||||
monaco.editor.defineTheme("light", light);
|
||||
}
|
||||
}}
|
||||
onMount={(editor, monaco) => {
|
||||
editorRef.current = editor;
|
||||
editor.updateOptions({
|
||||
glyphMargin: true,
|
||||
readOnly: true,
|
||||
});
|
||||
}}
|
||||
theme={theme === "dark" ? "dark" : "light"}
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,21 +6,28 @@ import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
import { styled } from "../stitches.config";
|
||||
|
||||
const overlayShow = keyframes({
|
||||
"0%": { opacity: 0 },
|
||||
"0%": { opacity: 0.01 },
|
||||
"100%": { opacity: 1 },
|
||||
});
|
||||
|
||||
const contentShow = keyframes({
|
||||
"0%": { opacity: 0, transform: "translate(-50%, -48%) scale(.96)" },
|
||||
"100%": { opacity: 1, transform: "translate(-50%, -50%) scale(1)" },
|
||||
"0%": { opacity: 0.01 },
|
||||
"100%": { opacity: 1 },
|
||||
});
|
||||
const StyledOverlay = styled(DialogPrimitive.Overlay, {
|
||||
zIndex: 1000,
|
||||
zIndex: 9999,
|
||||
backgroundColor: blackA.blackA9,
|
||||
position: "fixed",
|
||||
inset: 0,
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
display: "grid",
|
||||
placeItems: "center",
|
||||
overflowY: "auto",
|
||||
"@media (prefers-reduced-motion: no-preference)": {
|
||||
animation: `${overlayShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
|
||||
animation: `${overlayShow} 250ms cubic-bezier(0.16, 1, 0.3, 1)`,
|
||||
},
|
||||
".dark &": {
|
||||
backgroundColor: blackA.blackA11,
|
||||
@@ -32,15 +39,12 @@ const StyledContent = styled(DialogPrimitive.Content, {
|
||||
backgroundColor: "$mauve2",
|
||||
color: "$mauve12",
|
||||
borderRadius: "$md",
|
||||
position: "relative",
|
||||
boxShadow:
|
||||
"0px 10px 38px -5px rgba(22, 23, 24, 0.25), 0px 10px 20px -5px rgba(22, 23, 24, 0.2)",
|
||||
position: "fixed",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
width: "90vw",
|
||||
maxWidth: "450px",
|
||||
maxHeight: "85vh",
|
||||
// maxHeight: "85vh",
|
||||
padding: 25,
|
||||
"@media (prefers-reduced-motion: no-preference)": {
|
||||
animation: `${contentShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
|
||||
@@ -55,10 +59,9 @@ const StyledContent = styled(DialogPrimitive.Content, {
|
||||
|
||||
const Content: React.FC<{ css?: Stiches.CSS }> = ({ css, children }) => {
|
||||
return (
|
||||
<div>
|
||||
<StyledOverlay />
|
||||
<StyledOverlay>
|
||||
<StyledContent css={css}>{children}</StyledContent>
|
||||
</div>
|
||||
</StyledOverlay>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -83,3 +86,4 @@ export const DialogContent = Content;
|
||||
export const DialogTitle = StyledTitle;
|
||||
export const DialogDescription = StyledDescription;
|
||||
export const DialogClose = DialogPrimitive.Close;
|
||||
export const DialogPortal = DialogPrimitive.Portal;
|
||||
|
||||
@@ -71,7 +71,7 @@ const itemStyles = {
|
||||
},
|
||||
|
||||
"&:focus": {
|
||||
backgroundColor: "$pink9",
|
||||
backgroundColor: "$purple9",
|
||||
color: "$white",
|
||||
},
|
||||
};
|
||||
@@ -85,8 +85,8 @@ const StyledRadioItem = styled(DropdownMenuPrimitive.RadioItem, {
|
||||
});
|
||||
const StyledTriggerItem = styled(DropdownMenuPrimitive.TriggerItem, {
|
||||
'&[data-state="open"]': {
|
||||
backgroundColor: "$pink9",
|
||||
color: "$pink9",
|
||||
backgroundColor: "$purple9",
|
||||
color: "$purple9",
|
||||
},
|
||||
...itemStyles,
|
||||
});
|
||||
|
||||
@@ -25,8 +25,14 @@ import {
|
||||
import NewWindow from "react-new-window";
|
||||
import { signOut, useSession } from "next-auth/react";
|
||||
import { useSnapshot } from "valtio";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
import { createNewFile, syncToGist, updateEditorSettings, downloadAsZip } from "../state/actions";
|
||||
import {
|
||||
createNewFile,
|
||||
syncToGist,
|
||||
updateEditorSettings,
|
||||
downloadAsZip,
|
||||
} from "../state/actions";
|
||||
import state from "../state";
|
||||
import Box from "./Box";
|
||||
import Button from "./Button";
|
||||
@@ -43,7 +49,7 @@ import Flex from "./Flex";
|
||||
import Stack from "./Stack";
|
||||
import Input from "./Input";
|
||||
import Text from "./Text";
|
||||
import toast from "react-hot-toast";
|
||||
import Tooltip from "./Tooltip";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogContent,
|
||||
@@ -54,10 +60,8 @@ import {
|
||||
} from "./AlertDialog";
|
||||
import { styled } from "../stitches.config";
|
||||
|
||||
const DEFAULT_EXTENSION = ".c";
|
||||
|
||||
const ErrorText = styled(Text, {
|
||||
color: "$red9",
|
||||
color: "$error",
|
||||
mt: "$1",
|
||||
display: "block",
|
||||
});
|
||||
@@ -85,25 +89,39 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
||||
|
||||
const validateFilename = useCallback(
|
||||
(filename: string): { error: string | null } => {
|
||||
if (snap.files.find(file => file.name === filename)) {
|
||||
// check if filename already exists
|
||||
if (!filename) {
|
||||
return { error: "You need to add filename" };
|
||||
}
|
||||
if (snap.files.find((file) => file.name === filename)) {
|
||||
return { error: "Filename already exists." };
|
||||
}
|
||||
// More checks in future
|
||||
|
||||
if (!filename.includes(".") || filename[filename.length - 1] === ".") {
|
||||
return { error: "Filename should include file extension" };
|
||||
}
|
||||
|
||||
// check for illegal characters
|
||||
const ALPHA_NUMERICAL_REGEX = /^[A-Za-z0-9_-]+[.][A-Za-z0-9]{1,4}$/g;
|
||||
if (!filename.match(ALPHA_NUMERICAL_REGEX)) {
|
||||
return {
|
||||
error: `Filename can contain only characters from a-z, A-Z, 0-9, "_" and "-" and it needs to have file extension (e.g. ".c")`,
|
||||
};
|
||||
}
|
||||
return { error: null };
|
||||
},
|
||||
[snap.files]
|
||||
);
|
||||
const handleConfirm = useCallback(() => {
|
||||
// add default extension in case omitted
|
||||
let _filename = filename.includes(".") ? filename : filename + DEFAULT_EXTENSION;
|
||||
const chk = validateFilename(_filename);
|
||||
if (chk.error) {
|
||||
const chk = validateFilename(filename);
|
||||
if (chk && chk.error) {
|
||||
setNewfileError(`Error: ${chk.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsNewfileDialogOpen(false);
|
||||
createNewFile(_filename);
|
||||
createNewFile(filename);
|
||||
setFilename("");
|
||||
}, [filename, setIsNewfileDialogOpen, setFilename, validateFilename]);
|
||||
|
||||
@@ -139,7 +157,9 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
||||
return (
|
||||
<Button
|
||||
size="sm"
|
||||
outline={showWat ? snap.activeWat !== index : snap.active !== index}
|
||||
outline={
|
||||
showWat ? snap.activeWat !== index : snap.active !== index
|
||||
}
|
||||
onClick={() => (state.active = index)}
|
||||
key={file.name + index}
|
||||
css={{
|
||||
@@ -174,7 +194,8 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
||||
// If deleted file is behind active tab
|
||||
// we keep the current state otherwise
|
||||
// select previous file on the list
|
||||
state.active = index > snap.active ? snap.active : snap.active - 1;
|
||||
state.active =
|
||||
index > snap.active ? snap.active : snap.active - 1;
|
||||
}}
|
||||
>
|
||||
<X size="9px" weight="bold" />
|
||||
@@ -184,10 +205,18 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
||||
);
|
||||
})}
|
||||
{!showWat && (
|
||||
<Dialog open={isNewfileDialogOpen} onOpenChange={setIsNewfileDialogOpen}>
|
||||
<Dialog
|
||||
open={isNewfileDialogOpen}
|
||||
onOpenChange={setIsNewfileDialogOpen}
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
<Button ghost size="sm" css={{ alignItems: "center", px: "$2", mr: "$3" }}>
|
||||
<Plus size="16px" /> {snap.files.length === 0 && "Add new file"}
|
||||
<Button
|
||||
ghost
|
||||
size="sm"
|
||||
css={{ alignItems: "center", px: "$2", mr: "$3" }}
|
||||
>
|
||||
<Plus size="16px" />{" "}
|
||||
{snap.files.length === 0 && "Add new file"}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
@@ -196,8 +225,8 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
||||
<label>Filename</label>
|
||||
<Input
|
||||
value={filename}
|
||||
onChange={e => setFilename(e.target.value)}
|
||||
onKeyPress={e => {
|
||||
onChange={(e) => setFilename(e.target.value)}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
handleConfirm();
|
||||
}
|
||||
@@ -216,10 +245,7 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
||||
<DialogClose asChild>
|
||||
<Button outline>Cancel</Button>
|
||||
</DialogClose>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleConfirm}
|
||||
>
|
||||
<Button variant="primary" onClick={handleConfirm}>
|
||||
Create file
|
||||
</Button>
|
||||
</Flex>
|
||||
@@ -237,11 +263,13 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
||||
<Flex
|
||||
css={{
|
||||
py: "$3",
|
||||
backgroundColor: "$mauve3",
|
||||
backgroundColor: "$mauve2",
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
<Container css={{ width: "unset", display: "flex", alignItems: "center" }}>
|
||||
<Container
|
||||
css={{ width: "unset", display: "flex", alignItems: "center" }}
|
||||
>
|
||||
{status === "authenticated" ? (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@@ -274,10 +302,15 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem disabled onClick={() => signOut()}>
|
||||
<User size="16px" /> {session?.user?.username} ({session?.user.name})
|
||||
<User size="16px" /> {session?.user?.username} (
|
||||
{session?.user.name})
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => window.open(`http://gist.github.com/${session?.user.username}`)}
|
||||
onClick={() =>
|
||||
window.open(
|
||||
`http://gist.github.com/${session?.user.username}`
|
||||
)
|
||||
}
|
||||
>
|
||||
<ArrowSquareOut size="16px" />
|
||||
Go to your Gist
|
||||
@@ -291,7 +324,12 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
) : (
|
||||
<Button outline size="sm" css={{ mr: "$3" }} onClick={() => setPopUp(true)}>
|
||||
<Button
|
||||
outline
|
||||
size="sm"
|
||||
css={{ mr: "$3" }}
|
||||
onClick={() => setPopUp(true)}
|
||||
>
|
||||
<GithubLogo size="16px" /> Login
|
||||
</Button>
|
||||
)}
|
||||
@@ -330,36 +368,65 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Button isLoading={snap.zipLoading} onClick={downloadAsZip} outline size="sm" css={{ alignItems: "center" }}>
|
||||
<DownloadSimple size="16px" />
|
||||
</Button>
|
||||
<Button
|
||||
outline
|
||||
size="sm"
|
||||
css={{ alignItems: "center" }}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(`${window.location.origin}/develop/${snap.gistId}`);
|
||||
toast.success("Copied share link to clipboard!");
|
||||
}}
|
||||
<Tooltip content="Download as ZIP">
|
||||
<Button
|
||||
isLoading={snap.zipLoading}
|
||||
onClick={downloadAsZip}
|
||||
outline
|
||||
size="sm"
|
||||
css={{ alignItems: "center" }}
|
||||
>
|
||||
<DownloadSimple size="16px" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip content="Copy share link to clipboard">
|
||||
<Button
|
||||
outline
|
||||
size="sm"
|
||||
css={{ alignItems: "center" }}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
`${window.location.origin}/develop/${snap.gistId}`
|
||||
);
|
||||
toast.success("Copied share link to clipboard!");
|
||||
}}
|
||||
>
|
||||
<Share size="16px" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content={
|
||||
session && session.user
|
||||
? snap.gistOwner === session?.user.username
|
||||
? "Sync to Gist"
|
||||
: "Create as a new Gist"
|
||||
: "You need to be logged in to sync with Gist"
|
||||
}
|
||||
>
|
||||
<Share size="16px" />
|
||||
</Button>
|
||||
<Button
|
||||
outline
|
||||
size="sm"
|
||||
disabled={!session || !session.user}
|
||||
isLoading={snap.gistLoading}
|
||||
css={{ alignItems: "center" }}
|
||||
onClick={() => {
|
||||
if (snap.gistOwner === session?.user.username) {
|
||||
syncToGist(session);
|
||||
} else {
|
||||
setCreateNewAlertOpen(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<CloudArrowUp size="16px" />
|
||||
</Button>
|
||||
<Button
|
||||
outline
|
||||
size="sm"
|
||||
isDisabled={!session || !session.user}
|
||||
isLoading={snap.gistLoading}
|
||||
css={{ alignItems: "center" }}
|
||||
onClick={() => {
|
||||
if (!session || !session.user) {
|
||||
return;
|
||||
}
|
||||
if (snap.gistOwner === session?.user.username) {
|
||||
syncToGist(session);
|
||||
} else {
|
||||
setCreateNewAlertOpen(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{snap.gistOwner === session?.user.username ? (
|
||||
<CloudArrowUp size="16px" />
|
||||
) : (
|
||||
<FilePlus size="16px" />
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@@ -368,7 +435,10 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem disabled={snap.zipLoading} onClick={downloadAsZip}>
|
||||
<DropdownMenuItem
|
||||
disabled={snap.zipLoading}
|
||||
onClick={downloadAsZip}
|
||||
>
|
||||
<DownloadSimple size="16px" /> Download as ZIP
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
@@ -383,7 +453,9 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
||||
Copy share link to clipboard
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
disabled={session?.user.username !== snap.gistOwner || !snap.gistId}
|
||||
disabled={
|
||||
session?.user.username !== snap.gistOwner || !snap.gistId
|
||||
}
|
||||
onClick={() => {
|
||||
syncToGist(session);
|
||||
}}
|
||||
@@ -409,15 +481,21 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
||||
</DropdownMenu>
|
||||
</Stack>
|
||||
|
||||
{popup && !session ? <NewWindow center="parent" url="/sign-in" /> : null}
|
||||
{popup && !session ? (
|
||||
<NewWindow center="parent" url="/sign-in" />
|
||||
) : null}
|
||||
</Container>
|
||||
</Flex>
|
||||
<AlertDialog open={createNewAlertOpen} onOpenChange={value => setCreateNewAlertOpen(value)}>
|
||||
<AlertDialog
|
||||
open={createNewAlertOpen}
|
||||
onOpenChange={(value) => setCreateNewAlertOpen(value)}
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action will create new <strong>public</strong> Github Gist from your current saved
|
||||
files. You can delete gist anytime from your GitHub Gists page.
|
||||
This action will create new <strong>public</strong> Github Gist from
|
||||
your current saved files. You can delete gist anytime from your
|
||||
GitHub Gists page.
|
||||
</AlertDialogDescription>
|
||||
<Flex css={{ justifyContent: "flex-end", gap: "$3" }}>
|
||||
<AlertDialogCancel asChild>
|
||||
@@ -451,8 +529,8 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
||||
type="number"
|
||||
min="1"
|
||||
value={editorSettings.tabSize}
|
||||
onChange={e =>
|
||||
setEditorSettings(curr => ({
|
||||
onChange={(e) =>
|
||||
setEditorSettings((curr) => ({
|
||||
...curr,
|
||||
tabSize: Number(e.target.value),
|
||||
}))
|
||||
@@ -462,12 +540,18 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
||||
|
||||
<Flex css={{ marginTop: 25, justifyContent: "flex-end", gap: "$3" }}>
|
||||
<DialogClose asChild>
|
||||
<Button outline onClick={() => updateEditorSettings(editorSettings)}>
|
||||
<Button
|
||||
outline
|
||||
onClick={() => updateEditorSettings(editorSettings)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button variant="primary" onClick={() => updateEditorSettings(editorSettings)}>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => updateEditorSettings(editorSettings)}
|
||||
>
|
||||
Save changes
|
||||
</Button>
|
||||
</DialogClose>
|
||||
|
||||
@@ -16,9 +16,37 @@ const Flex = styled(Box, {
|
||||
},
|
||||
fluid: {
|
||||
true: {
|
||||
width: '100%'
|
||||
}
|
||||
}
|
||||
width: "100%",
|
||||
},
|
||||
},
|
||||
align: {
|
||||
start: {
|
||||
alignItems: "start",
|
||||
},
|
||||
center: {
|
||||
alignItems: "center",
|
||||
},
|
||||
end: {
|
||||
alignItems: "end",
|
||||
},
|
||||
},
|
||||
justify: {
|
||||
start: {
|
||||
justifyContent: "start",
|
||||
},
|
||||
center: {
|
||||
justifyContent: "center",
|
||||
},
|
||||
end: {
|
||||
justifyContent: "end",
|
||||
},
|
||||
"space-between": {
|
||||
justifyContent: "space-between",
|
||||
},
|
||||
"space-around": {
|
||||
justifyContent: "space-around",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import type monaco from "monaco-editor";
|
||||
import { ArrowBendLeftUp } from "phosphor-react";
|
||||
import { useTheme } from "next-themes";
|
||||
import { useRouter } from "next/router";
|
||||
import uniqBy from "lodash.uniqby";
|
||||
|
||||
import Box from "./Box";
|
||||
import Container from "./Container";
|
||||
@@ -21,6 +22,8 @@ import { createLanguageClient, createWebSocket } from "../utils/languageClient";
|
||||
import { listen } from "@codingame/monaco-jsonrpc";
|
||||
import ReconnectingWebSocket from "reconnecting-websocket";
|
||||
|
||||
import docs from "../xrpl-hooks-docs/docs";
|
||||
|
||||
loader.config({
|
||||
paths: {
|
||||
vs: "https://cdn.jsdelivr.net/npm/monaco-editor@0.30.1/min/vs",
|
||||
@@ -29,15 +32,78 @@ loader.config({
|
||||
|
||||
const validateWritability = (editor: monaco.editor.IStandaloneCodeEditor) => {
|
||||
const currPath = editor.getModel()?.uri.path;
|
||||
if (apiHeaderFiles.find(h => currPath?.endsWith(h))) {
|
||||
if (apiHeaderFiles.find((h) => currPath?.endsWith(h))) {
|
||||
editor.updateOptions({ readOnly: true });
|
||||
} else {
|
||||
editor.updateOptions({ readOnly: false });
|
||||
}
|
||||
};
|
||||
|
||||
let decorations: { [key: string]: string[] } = {};
|
||||
|
||||
const setMarkers = (monacoE: typeof monaco) => {
|
||||
// Get all the markers that are active at the moment,
|
||||
// Also if same error is there twice, we can show the content
|
||||
// only once (that's why we're using uniqBy)
|
||||
const markers = uniqBy(
|
||||
monacoE.editor
|
||||
.getModelMarkers({})
|
||||
// Filter out the markers that are hooks specific
|
||||
.filter(
|
||||
(marker) =>
|
||||
typeof marker?.code === "string" &&
|
||||
// Take only markers that starts with "hooks-"
|
||||
marker?.code?.includes("hooks-")
|
||||
),
|
||||
"code"
|
||||
);
|
||||
|
||||
// Get the active model (aka active file you're editing)
|
||||
// const model = monacoE.editor?.getModel(
|
||||
// monacoE.Uri.parse(`file:///work/c/${state.files?.[state.active]?.name}`)
|
||||
// );
|
||||
// console.log(state.active);
|
||||
// Add decoration (aka extra hoverMessages) to markers in the
|
||||
// exact same range (location) where the markers are
|
||||
const models = monacoE.editor.getModels();
|
||||
models.forEach((model) => {
|
||||
decorations[model.id] = model?.deltaDecorations(
|
||||
decorations?.[model.id] || [],
|
||||
markers
|
||||
.filter((marker) =>
|
||||
marker?.resource.path
|
||||
.split("/")
|
||||
.includes(`${state.files?.[state.active]?.name}`)
|
||||
)
|
||||
.map((marker) => ({
|
||||
range: new monacoE.Range(
|
||||
marker.startLineNumber,
|
||||
marker.startColumn,
|
||||
marker.endLineNumber,
|
||||
marker.endColumn
|
||||
),
|
||||
options: {
|
||||
hoverMessage: {
|
||||
value:
|
||||
// Find the related hover message markdown from the
|
||||
// /xrpl-hooks-docs/xrpl-hooks-docs-files.json file
|
||||
// which was generated from rst files
|
||||
|
||||
(typeof marker.code === "string" &&
|
||||
docs[marker?.code]?.toString()) ||
|
||||
"",
|
||||
supportHtml: true,
|
||||
isTrusted: true,
|
||||
},
|
||||
},
|
||||
}))
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const HooksEditor = () => {
|
||||
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>();
|
||||
const monacoRef = useRef<typeof monaco>();
|
||||
const subscriptionRef = useRef<ReconnectingWebSocket | null>(null);
|
||||
const snap = useSnapshot(state);
|
||||
const router = useRouter();
|
||||
@@ -52,6 +118,11 @@ const HooksEditor = () => {
|
||||
subscriptionRef?.current?.close();
|
||||
};
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (monacoRef.current) {
|
||||
setMarkers(monacoRef.current);
|
||||
}
|
||||
}, [snap.active]);
|
||||
return (
|
||||
<Box
|
||||
css={{
|
||||
@@ -60,7 +131,7 @@ const HooksEditor = () => {
|
||||
display: "flex",
|
||||
position: "relative",
|
||||
flexDirection: "column",
|
||||
backgroundColor: "$mauve3",
|
||||
backgroundColor: "$mauve2",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
@@ -71,15 +142,15 @@ const HooksEditor = () => {
|
||||
keepCurrentModel
|
||||
defaultLanguage={snap.files?.[snap.active]?.language}
|
||||
language={snap.files?.[snap.active]?.language}
|
||||
path={`file://work/c/${snap.files?.[snap.active]?.name}`}
|
||||
path={`file:///work/c/${snap.files?.[snap.active]?.name}`}
|
||||
defaultValue={snap.files?.[snap.active]?.content}
|
||||
beforeMount={monaco => {
|
||||
beforeMount={(monaco) => {
|
||||
if (!snap.editorCtx) {
|
||||
snap.files.forEach(file =>
|
||||
snap.files.forEach((file) =>
|
||||
monaco.editor.createModel(
|
||||
file.content,
|
||||
file.language,
|
||||
monaco.Uri.parse(`file://work/c/${file.name}`)
|
||||
monaco.Uri.parse(`file:///work/c/${file.name}`)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -100,7 +171,7 @@ const HooksEditor = () => {
|
||||
// listen when the web socket is opened
|
||||
listen({
|
||||
webSocket: webSocket as WebSocket,
|
||||
onConnection: connection => {
|
||||
onConnection: (connection) => {
|
||||
// create and start the language client
|
||||
const languageClient = createLanguageClient(connection);
|
||||
const disposable = languageClient.start();
|
||||
@@ -133,16 +204,45 @@ const HooksEditor = () => {
|
||||
}}
|
||||
onMount={(editor, monaco) => {
|
||||
editorRef.current = editor;
|
||||
monacoRef.current = monaco;
|
||||
editor.updateOptions({
|
||||
glyphMargin: true,
|
||||
lightbulb: {
|
||||
enabled: true,
|
||||
},
|
||||
});
|
||||
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
|
||||
saveFile();
|
||||
editor.addCommand(
|
||||
monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS,
|
||||
() => {
|
||||
saveFile();
|
||||
}
|
||||
);
|
||||
// When the markers (errors/warnings from clangd language server) change
|
||||
// Lets improve the markers by adding extra content to them from related
|
||||
// md files
|
||||
monaco.editor.onDidChangeMarkers(() => {
|
||||
if (monacoRef.current) {
|
||||
setMarkers(monacoRef.current);
|
||||
}
|
||||
});
|
||||
validateWritability(editor)
|
||||
|
||||
// Hacky way to hide Peek menu
|
||||
editor.onContextMenu((e) => {
|
||||
const host =
|
||||
document.querySelector<HTMLElement>(".shadow-root-host");
|
||||
|
||||
const contextMenuItems =
|
||||
host?.shadowRoot?.querySelectorAll("li.action-item");
|
||||
contextMenuItems?.forEach((k) => {
|
||||
// If menu item contains "Peek" lets hide it
|
||||
if (k.querySelector(".action-label")?.textContent === "Peek") {
|
||||
// @ts-expect-error
|
||||
k["style"].display = "none";
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
validateWritability(editor);
|
||||
}}
|
||||
theme={theme === "dark" ? "dark" : "light"}
|
||||
/>
|
||||
@@ -160,7 +260,9 @@ const HooksEditor = () => {
|
||||
<Box css={{ display: "inline-flex", pl: "35px" }}>
|
||||
<ArrowBendLeftUp size={30} />
|
||||
</Box>
|
||||
<Box css={{ pl: "0px", pt: "15px", flex: 1, display: "inline-flex" }}>
|
||||
<Box
|
||||
css={{ pl: "0px", pt: "15px", flex: 1, display: "inline-flex" }}
|
||||
>
|
||||
<Text
|
||||
css={{
|
||||
fontSize: "14px",
|
||||
@@ -168,7 +270,7 @@ const HooksEditor = () => {
|
||||
fontFamily: "$monospace",
|
||||
}}
|
||||
>
|
||||
Click the link above to create a your file
|
||||
Click the link above to create your file
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react";
|
||||
import { styled } from "../stitches.config";
|
||||
|
||||
export const Input = styled("input", {
|
||||
@@ -117,15 +118,17 @@ export const Input = styled("input", {
|
||||
},
|
||||
state: {
|
||||
invalid: {
|
||||
boxShadow: "inset 0 0 0 1px $colors$red7",
|
||||
boxShadow: "inset 0 0 0 1px $colors$crimson7",
|
||||
"&:focus": {
|
||||
boxShadow: "inset 0px 0px 0px 1px $colors$red8, 0px 0px 0px 1px $colors$red8",
|
||||
boxShadow:
|
||||
"inset 0px 0px 0px 1px $colors$crimson8, 0px 0px 0px 1px $colors$crimson8",
|
||||
},
|
||||
},
|
||||
valid: {
|
||||
boxShadow: "inset 0 0 0 1px $colors$green7",
|
||||
boxShadow: "inset 0 0 0 1px $colors$grass7",
|
||||
"&:focus": {
|
||||
boxShadow: "inset 0px 0px 0px 1px $colors$green8, 0px 0px 0px 1px $colors$green8",
|
||||
boxShadow:
|
||||
"inset 0px 0px 0px 1px $colors$grass8, 0px 0px 0px 1px $colors$grass8",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -146,4 +149,10 @@ export const Input = styled("input", {
|
||||
},
|
||||
});
|
||||
|
||||
export default Input;
|
||||
// eslint-disable-next-line react/display-name
|
||||
const ReffedInput = React.forwardRef<
|
||||
HTMLInputElement,
|
||||
React.ComponentProps<typeof Input>
|
||||
>((props, ref) => <Input {...props} ref={ref} />);
|
||||
|
||||
export default ReffedInput;
|
||||
|
||||
@@ -3,6 +3,14 @@ import { styled } from "../stitches.config";
|
||||
const StyledLink = styled("a", {
|
||||
color: "CurrentColor",
|
||||
textDecoration: "underline",
|
||||
cursor: 'pointer',
|
||||
variants: {
|
||||
highlighted: {
|
||||
true: {
|
||||
color: '$blue9'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default StyledLink;
|
||||
|
||||
@@ -1,25 +1,39 @@
|
||||
import React, { useRef, useLayoutEffect } from "react";
|
||||
import {
|
||||
useRef,
|
||||
useLayoutEffect,
|
||||
ReactNode,
|
||||
FC,
|
||||
useState,
|
||||
useCallback,
|
||||
} from "react";
|
||||
import { Notepad, Prohibit } from "phosphor-react";
|
||||
import useStayScrolled from "react-stay-scrolled";
|
||||
import NextLink from "next/link";
|
||||
|
||||
import Container from "./Container";
|
||||
import Box from "./Box";
|
||||
import Flex from "./Flex";
|
||||
import LogText from "./LogText";
|
||||
import { ILog } from "../state";
|
||||
import Text from "./Text";
|
||||
import Button from "./Button";
|
||||
import Heading from "./Heading";
|
||||
import Link from "./Link";
|
||||
import state, { ILog } from "../state";
|
||||
import { Pre, Link, Heading, Button, Text, Flex, Box } from ".";
|
||||
import regexifyString from "regexify-string";
|
||||
import { useSnapshot } from "valtio";
|
||||
import { AccountDialog } from "./Accounts";
|
||||
|
||||
interface ILogBox {
|
||||
title: string;
|
||||
clearLog?: () => void;
|
||||
logs: ILog[];
|
||||
renderNav?: () => ReactNode;
|
||||
enhanced?: boolean;
|
||||
}
|
||||
|
||||
const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children }) => {
|
||||
const LogBox: FC<ILogBox> = ({
|
||||
title,
|
||||
clearLog,
|
||||
logs,
|
||||
children,
|
||||
renderNav,
|
||||
enhanced,
|
||||
}) => {
|
||||
const logRef = useRef<HTMLPreElement>(null);
|
||||
const { stayScrolled /*, scrollBottom*/ } = useStayScrolled(logRef);
|
||||
|
||||
@@ -36,10 +50,24 @@ const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children }) => {
|
||||
background: "$mauve1",
|
||||
position: "relative",
|
||||
flex: 1,
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<Container css={{ px: 0, flexShrink: 1 }}>
|
||||
<Flex css={{ py: "$3" }}>
|
||||
<Container
|
||||
css={{
|
||||
px: 0,
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
fluid
|
||||
css={{
|
||||
height: "48px",
|
||||
alignItems: "center",
|
||||
fontSize: "$sm",
|
||||
fontWeight: 300,
|
||||
}}
|
||||
>
|
||||
<Heading
|
||||
as="h3"
|
||||
css={{
|
||||
@@ -56,6 +84,15 @@ const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children }) => {
|
||||
>
|
||||
<Notepad size="15px" /> <Text css={{ lineHeight: 1 }}>{title}</Text>
|
||||
</Heading>
|
||||
<Flex
|
||||
row
|
||||
align="center"
|
||||
css={{
|
||||
width: "50%", // TODO make it max without breaking layout!
|
||||
}}
|
||||
>
|
||||
{renderNav?.()}
|
||||
</Flex>
|
||||
<Flex css={{ ml: "auto", gap: "$3", marginRight: "$3" }}>
|
||||
{clearLog && (
|
||||
<Button ghost size="xs" onClick={clearLog}>
|
||||
@@ -64,6 +101,7 @@ const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children }) => {
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Box
|
||||
as="pre"
|
||||
ref={logRef}
|
||||
@@ -73,29 +111,31 @@ const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children }) => {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
height: "160px",
|
||||
height: "calc(100% - 48px)", // 100% minus the logbox header height
|
||||
overflowY: "auto",
|
||||
fontSize: "13px",
|
||||
fontWeight: "$body",
|
||||
fontFamily: "$monospace",
|
||||
px: "$3",
|
||||
pb: "$2",
|
||||
whiteSpace: "normal",
|
||||
overflowY: "auto",
|
||||
}}
|
||||
>
|
||||
{logs?.map((log, index) => (
|
||||
<Box as="span" key={log.type + index}>
|
||||
{/* <LogText capitalize variant={log.type}>
|
||||
{log.type}:{" "}
|
||||
</LogText> */}
|
||||
<LogText variant={log.type}>
|
||||
{log.message}{" "}
|
||||
{log.link && (
|
||||
<NextLink href={log.link} shallow passHref>
|
||||
<Link as="a">{log.linkText}</Link>
|
||||
</NextLink>
|
||||
)}
|
||||
</LogText>
|
||||
<Box
|
||||
as="span"
|
||||
key={log.type + index}
|
||||
css={{
|
||||
"@hover": {
|
||||
"&:hover": {
|
||||
backgroundColor: enhanced ? "$backgroundAlt" : undefined,
|
||||
},
|
||||
},
|
||||
p: enhanced ? "$1" : undefined,
|
||||
my: enhanced ? "$1" : undefined,
|
||||
}}
|
||||
>
|
||||
<Log {...log} />
|
||||
</Box>
|
||||
))}
|
||||
{children}
|
||||
@@ -105,4 +145,79 @@ const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const Log: FC<ILog> = ({
|
||||
type,
|
||||
timestamp: timestamp,
|
||||
message: _message,
|
||||
link,
|
||||
linkText,
|
||||
defaultCollapsed,
|
||||
jsonData: _jsonData,
|
||||
}) => {
|
||||
const [expanded, setExpanded] = useState(!defaultCollapsed);
|
||||
const { accounts } = useSnapshot(state);
|
||||
const [dialogAccount, setDialogAccount] = useState<string | null>(null);
|
||||
|
||||
const enrichAccounts = useCallback(
|
||||
(str?: string): ReactNode => {
|
||||
if (!str || !accounts.length) return null;
|
||||
|
||||
const pattern = `(${accounts.map((acc) => acc.address).join("|")})`;
|
||||
const res = regexifyString({
|
||||
pattern: new RegExp(pattern, "gim"),
|
||||
decorator: (match, idx) => {
|
||||
const name = accounts.find((acc) => acc.address === match)?.name;
|
||||
return (
|
||||
<Link
|
||||
key={match + idx}
|
||||
as="a"
|
||||
onClick={() => setDialogAccount(match)}
|
||||
title={match}
|
||||
highlighted
|
||||
>
|
||||
{name || match}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
input: str,
|
||||
});
|
||||
|
||||
return <>{res}</>;
|
||||
},
|
||||
[accounts]
|
||||
);
|
||||
_message = _message.trim().replace(/\n /gi, "\n");
|
||||
const message = enrichAccounts(_message);
|
||||
const jsonData = enrichAccounts(_jsonData);
|
||||
|
||||
return (
|
||||
<>
|
||||
<AccountDialog
|
||||
setActiveAccountAddress={setDialogAccount}
|
||||
activeAccountAddress={dialogAccount}
|
||||
/>
|
||||
<LogText variant={type}>
|
||||
{timestamp && (
|
||||
<Text muted monospace>
|
||||
{timestamp}{" "}
|
||||
</Text>
|
||||
)}
|
||||
<Pre>{message} </Pre>
|
||||
{link && (
|
||||
<NextLink href={link} shallow passHref>
|
||||
<Link as="a">{linkText}</Link>
|
||||
</NextLink>
|
||||
)}
|
||||
{jsonData && (
|
||||
<Link onClick={() => setExpanded(!expanded)} as="a">
|
||||
{expanded ? "Collapse" : "Expand"}
|
||||
</Link>
|
||||
)}
|
||||
{expanded && jsonData && <Pre block>{jsonData}</Pre>}
|
||||
</LogText>
|
||||
<br />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default LogBox;
|
||||
|
||||
@@ -4,19 +4,20 @@ const Text = styled("span", {
|
||||
fontFamily: "$monospace",
|
||||
lineHeight: "$body",
|
||||
color: "$text",
|
||||
wordWrap: "break-word",
|
||||
variants: {
|
||||
variant: {
|
||||
log: {
|
||||
color: "$text",
|
||||
},
|
||||
warning: {
|
||||
color: "$yellow11",
|
||||
color: "$warning",
|
||||
},
|
||||
error: {
|
||||
color: "$red11",
|
||||
color: "$error",
|
||||
},
|
||||
success: {
|
||||
color: "$green11",
|
||||
color: "$success",
|
||||
},
|
||||
},
|
||||
capitalize: {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { styled } from "../stitches.config";
|
||||
|
||||
const SVG = styled("svg", {
|
||||
"& #path-one, & #path-two": {
|
||||
fill: "$text",
|
||||
"& #path": {
|
||||
fill: "$accent",
|
||||
},
|
||||
});
|
||||
function Logo({
|
||||
@@ -14,21 +14,18 @@ function Logo({
|
||||
}) {
|
||||
return (
|
||||
<SVG
|
||||
width={width || "1em"}
|
||||
height={height || "1em"}
|
||||
viewBox="0 0 28 22"
|
||||
width={width || "1.1em"}
|
||||
height={height || "1.1em"}
|
||||
viewBox="0 0 294 283"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
id="path-one"
|
||||
d="M19.603 3.87h2.3l-4.786 4.747a4.466 4.466 0 01-6.276 0L6.054 3.871h2.3l3.636 3.605a2.828 2.828 0 003.975 0l3.638-3.605zM8.325 17.069h-2.3l4.816-4.776a4.466 4.466 0 016.276 0l4.816 4.776h-2.3l-3.665-3.635a2.828 2.828 0 00-3.975 0l-3.668 3.635z"
|
||||
/>
|
||||
<path
|
||||
id="path-two"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1.556 9.769h4.751v1.555H1.556v10.072H0V0h1.556v9.769zM26.444 9.769h-4.751v1.555h4.751v10.072H28V0h-1.556v9.769z"
|
||||
id="path"
|
||||
d="M265.827 235L172.416 141.589L265.005 49H226.822L147.732 128.089H53.5514L27.4824 155.089H147.732L227.643 235H265.827Z"
|
||||
fill="#9D2DFF"
|
||||
/>
|
||||
</SVG>
|
||||
);
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
DialogTrigger,
|
||||
} from "./Dialog";
|
||||
import PanelBox from "./PanelBox";
|
||||
import { templateFileIds } from '../state/constants';
|
||||
import { templateFileIds } from "../state/constants";
|
||||
|
||||
const Navigation = () => {
|
||||
const router = useRouter();
|
||||
@@ -40,9 +40,11 @@ const Navigation = () => {
|
||||
as="nav"
|
||||
css={{
|
||||
display: "flex",
|
||||
backgroundColor: "$mauve1",
|
||||
borderBottom: "1px solid $mauve6",
|
||||
position: "relative",
|
||||
zIndex: 2003,
|
||||
height: "60px",
|
||||
}}
|
||||
>
|
||||
<Container
|
||||
@@ -60,7 +62,7 @@ const Navigation = () => {
|
||||
pr: "$4",
|
||||
}}
|
||||
>
|
||||
<Link href="/" passHref>
|
||||
<Link href={gistId ? `/develop/${gistId}` : "/develop"} passHref>
|
||||
<Box
|
||||
as="a"
|
||||
css={{
|
||||
@@ -69,7 +71,7 @@ const Navigation = () => {
|
||||
color: "$textColor",
|
||||
}}
|
||||
>
|
||||
<Logo width="30px" height="30px" />
|
||||
<Logo width="32px" height="32px" />
|
||||
</Box>
|
||||
</Link>
|
||||
<Flex
|
||||
@@ -83,20 +85,42 @@ const Navigation = () => {
|
||||
<Spinner />
|
||||
) : (
|
||||
<>
|
||||
<Heading css={{ lineHeight: 1 }}>{snap.files?.[0]?.name || "XRPL Hooks"}</Heading>
|
||||
<Text css={{ fontSize: "$xs", color: "$mauve10", lineHeight: 1 }}>
|
||||
<Heading css={{ lineHeight: 1 }}>
|
||||
{snap.files?.[0]?.name || "XRPL Hooks"}
|
||||
</Heading>
|
||||
<Text
|
||||
css={{ fontSize: "$xs", color: "$mauve10", lineHeight: 1 }}
|
||||
>
|
||||
{snap.files.length > 0 ? "Gist: " : "Playground"}
|
||||
<Text css={{ color: "$mauve12" }}>
|
||||
{snap.files.length > 0 &&
|
||||
`${snap.gistOwner || "-"}/${truncate(snap.gistId || "")}`}
|
||||
</Text>
|
||||
{snap.files.length > 0 && (
|
||||
<Link
|
||||
href={`https://gist.github.com/${snap.gistOwner || ""}/${
|
||||
snap.gistId || ""
|
||||
}`}
|
||||
passHref
|
||||
>
|
||||
<Text
|
||||
as="a"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
css={{ color: "$mauve12" }}
|
||||
>
|
||||
{`${snap.gistOwner || "-"}/${truncate(
|
||||
snap.gistId || ""
|
||||
)}`}
|
||||
</Text>
|
||||
</Link>
|
||||
)}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
{router.isReady && (
|
||||
<ButtonGroup css={{ marginLeft: "auto" }}>
|
||||
<Dialog open={snap.mainModalOpen} onOpenChange={open => (state.mainModalOpen = open)}>
|
||||
<Dialog
|
||||
open={snap.mainModalOpen}
|
||||
onOpenChange={(open) => (state.mainModalOpen = open)}
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
<Button outline>
|
||||
<FolderOpen size="15px" />
|
||||
@@ -104,7 +128,7 @@ const Navigation = () => {
|
||||
</DialogTrigger>
|
||||
<DialogContent
|
||||
css={{
|
||||
maxWidth: "100%",
|
||||
maxWidth: "1080px",
|
||||
width: "80vw",
|
||||
height: "80%",
|
||||
backgroundColor: "$mauve1 !important",
|
||||
@@ -130,10 +154,12 @@ const Navigation = () => {
|
||||
flexDirection: "column",
|
||||
p: "$7",
|
||||
height: "100%",
|
||||
backgroundColor: "$mauve2",
|
||||
"@md": {
|
||||
width: "30%",
|
||||
maxWidth: "300px",
|
||||
borderBottom: "0px",
|
||||
borderRight: "1px solid $colors$mauve5",
|
||||
borderRight: "1px solid $colors$mauve6",
|
||||
},
|
||||
}}
|
||||
>
|
||||
@@ -144,9 +170,11 @@ const Navigation = () => {
|
||||
alignItems: "center",
|
||||
gap: "$3",
|
||||
fontSize: "$xl",
|
||||
lineHeight: "$one",
|
||||
fontWeight: "$bold",
|
||||
}}
|
||||
>
|
||||
<Logo width="30px" height="30px" /> XRPL Hooks Editor
|
||||
<Logo width="48px" height="48px" /> XRPL Hooks Builder
|
||||
</DialogTitle>
|
||||
<DialogDescription as="div">
|
||||
<Text
|
||||
@@ -157,17 +185,20 @@ const Navigation = () => {
|
||||
mb: "$7",
|
||||
}}
|
||||
>
|
||||
Hooks add smart contract functionality to the XRP Ledger.
|
||||
Hooks add smart contract functionality to the XRP
|
||||
Ledger.
|
||||
</Text>
|
||||
<Flex css={{ flexDirection: "column", gap: "$2", mt: "$2" }}>
|
||||
<Flex
|
||||
css={{ flexDirection: "column", gap: "$2", mt: "$2" }}
|
||||
>
|
||||
<Text
|
||||
css={{
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
gap: "$3",
|
||||
color: "$green9",
|
||||
color: "$purple10",
|
||||
"&:hover": {
|
||||
color: "$green11 !important",
|
||||
color: "$purple11",
|
||||
},
|
||||
"&:focus": {
|
||||
outline: 0,
|
||||
@@ -178,7 +209,7 @@ const Navigation = () => {
|
||||
target="_blank"
|
||||
href="https://github.com/XRPL-Labs/xrpld-hooks"
|
||||
>
|
||||
<ArrowUpRight size="15px" /> Developing Hooks
|
||||
<ArrowUpRight size="15px" /> Hooks Github
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
@@ -186,9 +217,9 @@ const Navigation = () => {
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
gap: "$3",
|
||||
color: "$green9",
|
||||
color: "$purple10",
|
||||
"&:hover": {
|
||||
color: "$green11 !important",
|
||||
color: "$purple11",
|
||||
},
|
||||
"&:focus": {
|
||||
outline: 0,
|
||||
@@ -197,7 +228,7 @@ const Navigation = () => {
|
||||
as="a"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
href="https://xrpl-hooks.readme.io/docs"
|
||||
href="https://xrpl-hooks.readme.io/v2.0/docs"
|
||||
>
|
||||
<ArrowUpRight size="15px" /> Hooks documentation
|
||||
</Text>
|
||||
@@ -206,9 +237,9 @@ const Navigation = () => {
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
gap: "$3",
|
||||
color: "$green9",
|
||||
color: "$purple10",
|
||||
"&:hover": {
|
||||
color: "$green11 !important",
|
||||
color: "$purple11",
|
||||
},
|
||||
"&:focus": {
|
||||
outline: 0,
|
||||
@@ -224,51 +255,67 @@ const Navigation = () => {
|
||||
</Flex>
|
||||
</DialogDescription>
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
css={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr",
|
||||
gridTemplateRows: "max-content",
|
||||
flex: 1,
|
||||
p: "$7",
|
||||
gap: "$3",
|
||||
alignItems: "flex-start",
|
||||
flexWrap: "wrap",
|
||||
backgroundImage: `url('/pattern.svg'), url('/pattern-2.svg')`,
|
||||
backgroundRepeat: "no-repeat",
|
||||
backgroundPosition: "bottom left, top right",
|
||||
"@md": {
|
||||
gridTemplateColumns: "1fr 1fr 1fr",
|
||||
<div>
|
||||
<Flex
|
||||
css={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr",
|
||||
gridTemplateRows: "max-content",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<PanelBox as="a" href={`/develop/${templateFileIds.starter}`}>
|
||||
<Heading>Starter</Heading>
|
||||
<Text>Just an empty starter with essential imports</Text>
|
||||
</PanelBox>
|
||||
<PanelBox as="a" href={`/develop/${templateFileIds.starter}`}>
|
||||
<Heading>Firewall</Heading>
|
||||
<Text>This Hook essentially checks a blacklist of accounts</Text>
|
||||
</PanelBox>
|
||||
<PanelBox as="a" href={`/develop/${templateFileIds.accept}`}>
|
||||
<Heading>Accept</Heading>
|
||||
<Text>This hook just accepts any transaction coming through it</Text>
|
||||
</PanelBox>
|
||||
<PanelBox as="a" href={`/develop/${templateFileIds.notary}`}>
|
||||
<Heading>Notary</Heading>
|
||||
<Text>Collecting signatures for multi-sign transactions</Text>
|
||||
</PanelBox>
|
||||
<PanelBox as="a" href={`/develop/${templateFileIds.carbon}`}>
|
||||
<Heading>Carbon</Heading>
|
||||
<Text>Send a percentage of sum to an address</Text>
|
||||
</PanelBox>
|
||||
<PanelBox as="a" href={`/develop/${templateFileIds.peggy}`}>
|
||||
<Heading>Peggy</Heading>
|
||||
<Text>An oracle based stabe coin hook</Text>
|
||||
</PanelBox>
|
||||
</Flex>
|
||||
flex: 1,
|
||||
p: "$7",
|
||||
gap: "$3",
|
||||
alignItems: "normal",
|
||||
flexWrap: "wrap",
|
||||
backgroundColor: "$mauve1",
|
||||
"@md": {
|
||||
gridTemplateColumns: "1fr 1fr 1fr",
|
||||
gridTemplateRows: "max-content",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<PanelBox
|
||||
as="a"
|
||||
href={`/develop/${templateFileIds.starter}`}
|
||||
>
|
||||
<Heading>Starter</Heading>
|
||||
<Text>
|
||||
Just a basic starter with essential imports
|
||||
</Text>
|
||||
</PanelBox>
|
||||
<PanelBox
|
||||
as="a"
|
||||
href={`/develop/${templateFileIds.firewall}`}
|
||||
>
|
||||
<Heading>Firewall</Heading>
|
||||
<Text>
|
||||
This Hook essentially checks a blacklist of accounts
|
||||
</Text>
|
||||
</PanelBox>
|
||||
<PanelBox
|
||||
as="a"
|
||||
href={`/develop/${templateFileIds.notary}`}
|
||||
>
|
||||
<Heading>Notary</Heading>
|
||||
<Text>
|
||||
Collecting signatures for multi-sign transactions
|
||||
</Text>
|
||||
</PanelBox>
|
||||
<PanelBox
|
||||
as="a"
|
||||
href={`/develop/${templateFileIds.carbon}`}
|
||||
>
|
||||
<Heading>Carbon</Heading>
|
||||
<Text>Send a percentage of sum to an address</Text>
|
||||
</PanelBox>
|
||||
<PanelBox
|
||||
as="a"
|
||||
href={`/develop/${templateFileIds.peggy}`}
|
||||
>
|
||||
<Heading>Peggy</Heading>
|
||||
<Text>An oracle based stable coin hook</Text>
|
||||
</PanelBox>
|
||||
</Flex>
|
||||
</div>
|
||||
</Flex>
|
||||
<DialogClose asChild>
|
||||
<Box
|
||||
@@ -313,25 +360,53 @@ const Navigation = () => {
|
||||
}}
|
||||
>
|
||||
<ButtonGroup>
|
||||
<Link href={gistId ? `/develop/${gistId}` : "/develop"} passHref shallow>
|
||||
<Button as="a" outline={!router.pathname.includes("/develop")} uppercase>
|
||||
<Link
|
||||
href={gistId ? `/develop/${gistId}` : "/develop"}
|
||||
passHref
|
||||
shallow
|
||||
>
|
||||
<Button
|
||||
as="a"
|
||||
outline={!router.pathname.includes("/develop")}
|
||||
uppercase
|
||||
>
|
||||
Develop
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href={gistId ? `/deploy/${gistId}` : "/deploy"} passHref shallow>
|
||||
<Button as="a" outline={!router.pathname.includes("/deploy")} uppercase>
|
||||
<Link
|
||||
href={gistId ? `/deploy/${gistId}` : "/deploy"}
|
||||
passHref
|
||||
shallow
|
||||
>
|
||||
<Button
|
||||
as="a"
|
||||
outline={!router.pathname.includes("/deploy")}
|
||||
uppercase
|
||||
>
|
||||
Deploy
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href={gistId ? `/test/${gistId}` : "/test"} passHref shallow>
|
||||
<Button as="a" outline={!router.pathname.includes("/test")} uppercase>
|
||||
<Link
|
||||
href={gistId ? `/test/${gistId}` : "/test"}
|
||||
passHref
|
||||
shallow
|
||||
>
|
||||
<Button
|
||||
as="a"
|
||||
outline={!router.pathname.includes("/test")}
|
||||
uppercase
|
||||
>
|
||||
Test
|
||||
</Button>
|
||||
</Link>
|
||||
</ButtonGroup>
|
||||
<Button outline disabled>
|
||||
<BookOpen size="15px" />
|
||||
</Button>
|
||||
<Link href="https://xrpl-hooks.readme.io/v2.0" passHref>
|
||||
<a target="_blank" rel="noreferrer noopener">
|
||||
<Button outline>
|
||||
<BookOpen size="15px" />
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</Stack>
|
||||
</Flex>
|
||||
</Container>
|
||||
|
||||
@@ -5,8 +5,8 @@ import Text from "./Text";
|
||||
const PanelBox = styled("div", {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
border: "1px solid $colors$mauve5",
|
||||
backgroundColor: "$mauve1",
|
||||
border: "1px solid $colors$mauve6",
|
||||
backgroundColor: "$mauve2",
|
||||
padding: "$3",
|
||||
borderRadius: "$sm",
|
||||
fontWeight: "lighter",
|
||||
|
||||
27
components/Pre.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { styled } from "../stitches.config";
|
||||
|
||||
const Pre = styled("span", {
|
||||
m: 0,
|
||||
wordBreak: "break-all",
|
||||
fontFamily: '$monospace',
|
||||
whiteSpace: 'pre-wrap',
|
||||
variants: {
|
||||
fluid: {
|
||||
true: {
|
||||
width: "100%",
|
||||
},
|
||||
},
|
||||
line: {
|
||||
true: {
|
||||
whiteSpace: 'pre-line'
|
||||
}
|
||||
},
|
||||
block: {
|
||||
true: {
|
||||
display: 'block'
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export default Pre;
|
||||
@@ -1,56 +1,152 @@
|
||||
import { FC } from "react";
|
||||
import { mauve, mauveDark } from "@radix-ui/colors";
|
||||
import { forwardRef } from "react";
|
||||
import { mauve, mauveDark, purple, purpleDark } from "@radix-ui/colors";
|
||||
import { useTheme } from "next-themes";
|
||||
import { styled } from '../stitches.config';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { styled } from "../stitches.config";
|
||||
import dynamic from "next/dynamic";
|
||||
import type { Props } from "react-select";
|
||||
const SelectInput = dynamic(() => import("react-select"), { ssr: false });
|
||||
|
||||
const Select: FC<Props> = props => {
|
||||
// eslint-disable-next-line react/display-name
|
||||
const Select = forwardRef<any, Props>((props, ref) => {
|
||||
const { theme } = useTheme();
|
||||
const isDark = theme === "dark";
|
||||
const colors: any = {
|
||||
// primary: pink.pink9,
|
||||
active: isDark ? purpleDark.purple9 : purple.purple9,
|
||||
activeLight: isDark ? purpleDark.purple5 : purple.purple5,
|
||||
primary: isDark ? mauveDark.mauve4 : mauve.mauve4,
|
||||
secondary: isDark ? mauveDark.mauve8 : mauve.mauve8,
|
||||
background: isDark ? "rgb(10, 10, 10)" : "rgb(245, 245, 245)",
|
||||
background: isDark ? mauveDark.mauve4 : mauve.mauve4,
|
||||
searchText: isDark ? mauveDark.mauve12 : mauve.mauve12,
|
||||
bg: isDark ? mauveDark.mauve1 : mauve.mauve1,
|
||||
dropDownBg: isDark ? mauveDark.mauve5 : mauve.mauve2,
|
||||
mauve4: isDark ? mauveDark.mauve4 : mauve.mauve4,
|
||||
mauve5: isDark ? mauveDark.mauve5 : mauve.mauve5,
|
||||
mauve8: isDark ? mauveDark.mauve8 : mauve.mauve8,
|
||||
mauve9: isDark ? mauveDark.mauve9 : mauve.mauve9,
|
||||
mauve12: isDark ? mauveDark.mauve12 : mauve.mauve12,
|
||||
border: isDark ? mauveDark.mauve10 : mauve.mauve10,
|
||||
placeholder: isDark ? mauveDark.mauve11 : mauve.mauve11,
|
||||
};
|
||||
colors.outline = colors.background;
|
||||
colors.selected = colors.secondary;
|
||||
return (
|
||||
<SelectInput
|
||||
menuPosition="fixed"
|
||||
theme={theme => ({
|
||||
...theme,
|
||||
spacing: {
|
||||
...theme.spacing,
|
||||
controlHeight: 30
|
||||
ref={ref}
|
||||
menuPosition={props.menuPosition || "fixed"}
|
||||
styles={{
|
||||
container: (provided) => {
|
||||
return {
|
||||
...provided,
|
||||
position: "relative",
|
||||
};
|
||||
},
|
||||
colors: {
|
||||
primary: colors.selected,
|
||||
primary25: colors.primary,
|
||||
primary50: colors.primary,
|
||||
primary75: colors.primary,
|
||||
danger: colors.primary,
|
||||
dangerLight: colors.primary,
|
||||
neutral0: colors.background,
|
||||
neutral5: colors.primary,
|
||||
neutral10: colors.primary,
|
||||
neutral20: colors.outline,
|
||||
neutral30: colors.primary,
|
||||
neutral40: colors.primary,
|
||||
neutral50: colors.placeholder,
|
||||
neutral60: colors.primary,
|
||||
neutral70: colors.primary,
|
||||
neutral80: colors.searchText,
|
||||
neutral90: colors.primary,
|
||||
singleValue: (provided) => ({
|
||||
...provided,
|
||||
color: colors.mauve12,
|
||||
}),
|
||||
menu: (provided) => ({
|
||||
...provided,
|
||||
backgroundColor: colors.dropDownBg,
|
||||
}),
|
||||
control: (provided, state) => {
|
||||
return {
|
||||
...provided,
|
||||
border: "0px",
|
||||
backgroundColor: colors.mauve4,
|
||||
boxShadow: `0 0 0 1px ${
|
||||
state.isFocused ? colors.border : colors.secondary
|
||||
}`,
|
||||
};
|
||||
},
|
||||
})}
|
||||
input: (provided) => {
|
||||
return {
|
||||
...provided,
|
||||
color: "$text",
|
||||
};
|
||||
},
|
||||
multiValue: (provided) => {
|
||||
return {
|
||||
...provided,
|
||||
backgroundColor: colors.mauve8,
|
||||
};
|
||||
},
|
||||
multiValueLabel: (provided) => {
|
||||
return {
|
||||
...provided,
|
||||
color: colors.mauve12,
|
||||
};
|
||||
},
|
||||
multiValueRemove: (provided) => {
|
||||
return {
|
||||
...provided,
|
||||
":hover": {
|
||||
background: colors.mauve9,
|
||||
},
|
||||
};
|
||||
},
|
||||
option: (provided, state) => {
|
||||
return {
|
||||
...provided,
|
||||
color: colors.searchText,
|
||||
backgroundColor:
|
||||
state.isSelected || state.isFocused
|
||||
? colors.activeLight
|
||||
: colors.dropDownBg,
|
||||
":hover": {
|
||||
backgroundColor: colors.active,
|
||||
color: "#ffffff",
|
||||
},
|
||||
":selected": {
|
||||
backgroundColor: "red",
|
||||
},
|
||||
};
|
||||
},
|
||||
indicatorSeparator: (provided) => {
|
||||
return {
|
||||
...provided,
|
||||
backgroundColor: colors.secondary,
|
||||
};
|
||||
},
|
||||
dropdownIndicator: (provided, state) => {
|
||||
return {
|
||||
...provided,
|
||||
color: state.isFocused ? colors.border : colors.secondary,
|
||||
":hover": {
|
||||
color: colors.border,
|
||||
},
|
||||
};
|
||||
},
|
||||
}}
|
||||
// theme={(theme) => ({
|
||||
// ...theme,
|
||||
// spacing: {
|
||||
// ...theme.spacing,
|
||||
// controlHeight: 30,
|
||||
// },
|
||||
// colors: {
|
||||
// primary: colors.selected,
|
||||
// primary25: colors.active,
|
||||
// primary50: colors.primary,
|
||||
// primary75: colors.primary,
|
||||
// danger: colors.primary,
|
||||
// dangerLight: colors.primary,
|
||||
// neutral0: colors.background,
|
||||
// neutral5: colors.primary,
|
||||
// neutral10: colors.primary,
|
||||
// neutral20: colors.outline,
|
||||
// neutral30: colors.primary,
|
||||
// neutral40: colors.primary,
|
||||
// neutral50: colors.placeholder,
|
||||
// neutral60: colors.primary,
|
||||
// neutral70: colors.primary,
|
||||
// neutral80: colors.searchText,
|
||||
// neutral90: colors.primary,
|
||||
// },
|
||||
// })}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default styled(Select, {});
|
||||
|
||||
257
components/SetHookDialog.tsx
Normal file
@@ -0,0 +1,257 @@
|
||||
import React, { useState } from "react";
|
||||
import { Plus, Trash, X } from "phosphor-react";
|
||||
import Button from "./Button";
|
||||
import Box from "./Box";
|
||||
import { Stack, Flex, Select } from ".";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogClose,
|
||||
DialogTrigger,
|
||||
} from "./Dialog";
|
||||
import { Input } from "./Input";
|
||||
import {
|
||||
Controller,
|
||||
SubmitHandler,
|
||||
useFieldArray,
|
||||
useForm,
|
||||
} from "react-hook-form";
|
||||
|
||||
import { TTS, tts } from "../utils/hookOnCalculator";
|
||||
import { deployHook } from "../state/actions";
|
||||
import type { IAccount } from "../state";
|
||||
import { useSnapshot } from "valtio";
|
||||
import state from "../state";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
const transactionOptions = Object.keys(tts).map((key) => ({
|
||||
label: key,
|
||||
value: key as keyof TTS,
|
||||
}));
|
||||
|
||||
export type SetHookData = {
|
||||
Invoke: {
|
||||
value: keyof TTS;
|
||||
label: string;
|
||||
}[];
|
||||
HookParameters: {
|
||||
HookParameter: {
|
||||
HookParameterName: string;
|
||||
HookParameterValue: string;
|
||||
};
|
||||
}[];
|
||||
// HookGrants: {
|
||||
// HookGrant: {
|
||||
// Authorize: string;
|
||||
// HookHash: string;
|
||||
// };
|
||||
// }[];
|
||||
};
|
||||
|
||||
export const SetHookDialog: React.FC<{ account: IAccount }> = ({ account }) => {
|
||||
const snap = useSnapshot(state);
|
||||
const [isSetHookDialogOpen, setIsSetHookDialogOpen] = useState(false);
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
control,
|
||||
// formState: { errors },
|
||||
} = useForm<SetHookData>();
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
control,
|
||||
name: "HookParameters", // unique name for your Field Array
|
||||
});
|
||||
// const {
|
||||
// fields: grantFields,
|
||||
// append: grantAppend,
|
||||
// remove: grantRemove,
|
||||
// } = useFieldArray({
|
||||
// control,
|
||||
// name: "HookGrants", // unique name for your Field Array
|
||||
// });
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const onSubmit: SubmitHandler<SetHookData> = async (data) => {
|
||||
const currAccount = state.accounts.find(
|
||||
(acc) => acc.address === account.address
|
||||
);
|
||||
if (currAccount) currAccount.isLoading = true;
|
||||
const res = await deployHook(account, data);
|
||||
if (currAccount) currAccount.isLoading = false;
|
||||
|
||||
if (res && res.engine_result === "tesSUCCESS") {
|
||||
toast.success("Transaction succeeded!");
|
||||
return setIsSetHookDialogOpen(false);
|
||||
}
|
||||
toast.error(`Transaction failed! (${res?.engine_result_message})`);
|
||||
};
|
||||
return (
|
||||
<Dialog open={isSetHookDialogOpen} onOpenChange={setIsSetHookDialogOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
ghost
|
||||
size="xs"
|
||||
uppercase
|
||||
variant={"secondary"}
|
||||
disabled={
|
||||
account.isLoading ||
|
||||
!snap.files.filter((file) => file.compiledWatContent).length
|
||||
}
|
||||
>
|
||||
Set Hook
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<DialogTitle>Deploy configuration</DialogTitle>
|
||||
<DialogDescription as="div">
|
||||
<Stack css={{ width: "100%", flex: 1 }}>
|
||||
<Box css={{ width: "100%" }}>
|
||||
<label>Invoke on transactions</label>
|
||||
<Controller
|
||||
name="Invoke"
|
||||
control={control}
|
||||
defaultValue={transactionOptions.filter(
|
||||
(to) => to.label === "ttPAYMENT"
|
||||
)}
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
{...field}
|
||||
closeMenuOnSelect={false}
|
||||
isMulti
|
||||
menuPosition="fixed"
|
||||
options={transactionOptions}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
<Box css={{ width: "100%" }}>
|
||||
<label style={{ marginBottom: "10px", display: "block" }}>
|
||||
Hook parameters
|
||||
</label>
|
||||
<Stack>
|
||||
{fields.map((field, index) => (
|
||||
<Stack key={field.id}>
|
||||
<Input
|
||||
// important to include key with field's id
|
||||
placeholder="Parameter name"
|
||||
{...register(
|
||||
`HookParameters.${index}.HookParameter.HookParameterName`
|
||||
)}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Parameter value"
|
||||
{...register(
|
||||
`HookParameters.${index}.HookParameter.HookParameterValue`
|
||||
)}
|
||||
/>
|
||||
<Button onClick={() => remove(index)} variant="destroy">
|
||||
<Trash weight="regular" size="16px" />
|
||||
</Button>
|
||||
</Stack>
|
||||
))}
|
||||
<Button
|
||||
outline
|
||||
fullWidth
|
||||
type="button"
|
||||
onClick={() =>
|
||||
append({
|
||||
HookParameter: {
|
||||
HookParameterName: "",
|
||||
HookParameterValue: "",
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
<Plus size="16px" />
|
||||
Add Hook Parameter
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
{/* <Box css={{ width: "100%" }}>
|
||||
<label style={{ marginBottom: "10px", display: "block" }}>
|
||||
Hook Grants
|
||||
</label>
|
||||
<Stack>
|
||||
{grantFields.map((field, index) => (
|
||||
<Stack key={field.id}>
|
||||
<Input
|
||||
// important to include key with field's id
|
||||
placeholder="Authorize"
|
||||
{...register(
|
||||
`HookGrants.${index}.HookGrant.Authorize`,
|
||||
{ minLength: 5 }
|
||||
)}
|
||||
/>
|
||||
<Input
|
||||
placeholder="HookHash"
|
||||
{...register(`HookGrants.${index}.HookGrant.HookHash`, {
|
||||
minLength: 64,
|
||||
maxLength: 64,
|
||||
})}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => grantRemove(index)}
|
||||
variant="destroy"
|
||||
>
|
||||
<Trash weight="regular" size="16px" />
|
||||
</Button>
|
||||
</Stack>
|
||||
))}
|
||||
<Button
|
||||
outline
|
||||
fullWidth
|
||||
type="button"
|
||||
onClick={() =>
|
||||
grantAppend({
|
||||
HookGrant: {
|
||||
Authorize: "",
|
||||
HookHash: "",
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
<Plus size="16px" />
|
||||
Add Hook Grant
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box> */}
|
||||
</Stack>
|
||||
</DialogDescription>
|
||||
|
||||
<Flex
|
||||
css={{
|
||||
marginTop: 25,
|
||||
justifyContent: "flex-end",
|
||||
gap: "$3",
|
||||
}}
|
||||
>
|
||||
<DialogClose asChild>
|
||||
<Button outline>Cancel</Button>
|
||||
</DialogClose>
|
||||
{/* <DialogClose asChild> */}
|
||||
<Button
|
||||
variant="primary"
|
||||
type="submit"
|
||||
isLoading={account.isLoading}
|
||||
>
|
||||
Set Hook
|
||||
</Button>
|
||||
{/* </DialogClose> */}
|
||||
</Flex>
|
||||
<DialogClose asChild>
|
||||
<Box css={{ position: "absolute", top: "$3", right: "$3" }}>
|
||||
<X size="20px" />
|
||||
</Box>
|
||||
</DialogClose>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default SetHookDialog;
|
||||
@@ -1,24 +1,66 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import React, {
|
||||
useEffect,
|
||||
useState,
|
||||
Fragment,
|
||||
isValidElement,
|
||||
useCallback,
|
||||
} from "react";
|
||||
import type { ReactNode, ReactElement } from "react";
|
||||
import { Button, Stack } from ".";
|
||||
import { Box, Button, Flex, Input, Stack, Text } from ".";
|
||||
import {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogClose,
|
||||
} from "./Dialog";
|
||||
import { Plus, X } from "phosphor-react";
|
||||
import { styled } from "../stitches.config";
|
||||
|
||||
const ErrorText = styled(Text, {
|
||||
color: "$error",
|
||||
mt: "$1",
|
||||
display: "block",
|
||||
});
|
||||
|
||||
interface TabProps {
|
||||
header?: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
// TODO customise strings shown
|
||||
interface Props {
|
||||
activeIndex?: number;
|
||||
activeHeader?: string;
|
||||
headless?: boolean;
|
||||
children: ReactElement<TabProps>[];
|
||||
keepAllAlive?: boolean;
|
||||
defaultExtension?: string;
|
||||
forceDefaultExtension?: boolean;
|
||||
onCreateNewTab?: (name: string) => any;
|
||||
onCloseTab?: (index: number, header?: string) => any;
|
||||
}
|
||||
|
||||
export const Tab = (props: TabProps) => null;
|
||||
|
||||
export const Tabs = ({ children, activeIndex, activeHeader, headless }: Props) => {
|
||||
export const Tabs = ({
|
||||
children,
|
||||
activeIndex,
|
||||
activeHeader,
|
||||
headless,
|
||||
keepAllAlive = false,
|
||||
onCreateNewTab,
|
||||
onCloseTab,
|
||||
defaultExtension = "",
|
||||
forceDefaultExtension,
|
||||
}: Props) => {
|
||||
const [active, setActive] = useState(activeIndex || 0);
|
||||
const tabProps: TabProps[] = children.map(elem => elem.props);
|
||||
const tabs: TabProps[] = children.map((elem) => elem.props);
|
||||
|
||||
const [isNewtabDialogOpen, setIsNewtabDialogOpen] = useState(false);
|
||||
const [tabname, setTabname] = useState("");
|
||||
const [newtabError, setNewtabError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeIndex) setActive(activeIndex);
|
||||
@@ -26,10 +68,57 @@ export const Tabs = ({ children, activeIndex, activeHeader, headless }: Props) =
|
||||
|
||||
useEffect(() => {
|
||||
if (activeHeader) {
|
||||
const idx = tabProps.findIndex(tab => tab.header === activeHeader);
|
||||
const idx = tabs.findIndex((tab) => tab.header === activeHeader);
|
||||
setActive(idx);
|
||||
}
|
||||
}, [activeHeader, tabProps]);
|
||||
}, [activeHeader, tabs]);
|
||||
|
||||
// when filename changes, reset error
|
||||
useEffect(() => {
|
||||
setNewtabError(null);
|
||||
}, [tabname, setNewtabError]);
|
||||
|
||||
const validateTabname = useCallback(
|
||||
(tabname: string): { error: string | null } => {
|
||||
if (tabs.find((tab) => tab.header === tabname)) {
|
||||
return { error: "Name already exists." };
|
||||
}
|
||||
return { error: null };
|
||||
},
|
||||
[tabs]
|
||||
);
|
||||
|
||||
const handleCreateTab = useCallback(() => {
|
||||
// add default extension in case omitted
|
||||
let _tabname = tabname.includes(".") ? tabname : tabname + defaultExtension;
|
||||
if (forceDefaultExtension && !_tabname.endsWith(defaultExtension)) {
|
||||
_tabname = _tabname + defaultExtension;
|
||||
}
|
||||
|
||||
const chk = validateTabname(_tabname);
|
||||
if (chk.error) {
|
||||
setNewtabError(`Error: ${chk.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsNewtabDialogOpen(false);
|
||||
setTabname("");
|
||||
// switch to new tab?
|
||||
setActive(tabs.length);
|
||||
|
||||
onCreateNewTab?.(_tabname);
|
||||
}, [tabname, defaultExtension, validateTabname, onCreateNewTab, tabs.length]);
|
||||
|
||||
const handleCloseTab = useCallback(
|
||||
(idx: number) => {
|
||||
if (idx <= active && active !== 0) {
|
||||
setActive(active - 1);
|
||||
}
|
||||
|
||||
onCloseTab?.(idx, tabs[idx].header);
|
||||
},
|
||||
[active, onCloseTab, tabs]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -40,11 +129,13 @@ export const Tabs = ({ children, activeIndex, activeHeader, headless }: Props) =
|
||||
flex: 1,
|
||||
flexWrap: "nowrap",
|
||||
marginBottom: "-1px",
|
||||
width: "100%",
|
||||
overflow: "auto",
|
||||
}}
|
||||
>
|
||||
{tabProps.map((prop, idx) => (
|
||||
{tabs.map((tab, idx) => (
|
||||
<Button
|
||||
key={prop.header}
|
||||
key={tab.header}
|
||||
role="tab"
|
||||
tabIndex={idx}
|
||||
onClick={() => setActive(idx)}
|
||||
@@ -59,12 +150,108 @@ export const Tabs = ({ children, activeIndex, activeHeader, headless }: Props) =
|
||||
},
|
||||
}}
|
||||
>
|
||||
{prop.header || idx}
|
||||
{tab.header || idx}
|
||||
{onCloseTab && (
|
||||
<Box
|
||||
as="span"
|
||||
css={{
|
||||
display: "flex",
|
||||
p: "2px",
|
||||
borderRadius: "$full",
|
||||
mr: "-4px",
|
||||
"&:hover": {
|
||||
// boxSizing: "0px 0px 1px",
|
||||
backgroundColor: "$mauve2",
|
||||
color: "$mauve12",
|
||||
},
|
||||
}}
|
||||
onClick={(ev: React.MouseEvent<HTMLElement>) => {
|
||||
ev.stopPropagation();
|
||||
handleCloseTab(idx);
|
||||
}}
|
||||
>
|
||||
<X size="9px" weight="bold" />
|
||||
</Box>
|
||||
)}
|
||||
</Button>
|
||||
))}
|
||||
{onCreateNewTab && (
|
||||
<Dialog
|
||||
open={isNewtabDialogOpen}
|
||||
onOpenChange={setIsNewtabDialogOpen}
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
ghost
|
||||
size="sm"
|
||||
css={{ alignItems: "center", px: "$2", mr: "$3" }}
|
||||
>
|
||||
<Plus size="16px" /> {tabs.length === 0 && "Add new tab"}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogTitle>Create new tab</DialogTitle>
|
||||
<DialogDescription>
|
||||
<label>Tabname</label>
|
||||
<Input
|
||||
value={tabname}
|
||||
onChange={(e) => setTabname(e.target.value)}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
handleCreateTab();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ErrorText>{newtabError}</ErrorText>
|
||||
</DialogDescription>
|
||||
|
||||
<Flex
|
||||
css={{
|
||||
marginTop: 25,
|
||||
justifyContent: "flex-end",
|
||||
gap: "$3",
|
||||
}}
|
||||
>
|
||||
<DialogClose asChild>
|
||||
<Button outline>Cancel</Button>
|
||||
</DialogClose>
|
||||
<Button variant="primary" onClick={handleCreateTab}>
|
||||
Create
|
||||
</Button>
|
||||
</Flex>
|
||||
<DialogClose asChild>
|
||||
<Box css={{ position: "absolute", top: "$3", right: "$3" }}>
|
||||
<X size="20px" />
|
||||
</Box>
|
||||
</DialogClose>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
{tabProps[active].children}
|
||||
{keepAllAlive ? (
|
||||
tabs.map((tab, idx) => {
|
||||
// TODO Maybe rule out fragments as children
|
||||
if (!isValidElement(tab.children)) {
|
||||
if (active !== idx) return null;
|
||||
return tab.children;
|
||||
}
|
||||
let key = tab.children.key || tab.header || idx;
|
||||
let { children } = tab;
|
||||
let { style, ...props } = children.props;
|
||||
return (
|
||||
<children.type
|
||||
key={key}
|
||||
{...props}
|
||||
style={{ ...style, display: active !== idx ? "none" : undefined }}
|
||||
/>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<Fragment key={tabs[active].header || active}>
|
||||
{tabs[active].children}
|
||||
</Fragment>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,6 +14,11 @@ const Text = styled("span", {
|
||||
true: {
|
||||
color: '$mauve9'
|
||||
}
|
||||
},
|
||||
monospace: {
|
||||
true: {
|
||||
fontFamily: '$monospace'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
89
components/Tooltip.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import React from "react";
|
||||
import { styled, keyframes } from "../stitches.config";
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||
|
||||
const slideUpAndFade = keyframes({
|
||||
"0%": { opacity: 0, transform: "translateY(2px)" },
|
||||
"100%": { opacity: 1, transform: "translateY(0)" },
|
||||
});
|
||||
|
||||
const slideRightAndFade = keyframes({
|
||||
"0%": { opacity: 0, transform: "translateX(-2px)" },
|
||||
"100%": { opacity: 1, transform: "translateX(0)" },
|
||||
});
|
||||
|
||||
const slideDownAndFade = keyframes({
|
||||
"0%": { opacity: 0, transform: "translateY(-2px)" },
|
||||
"100%": { opacity: 1, transform: "translateY(0)" },
|
||||
});
|
||||
|
||||
const slideLeftAndFade = keyframes({
|
||||
"0%": { opacity: 0, transform: "translateX(2px)" },
|
||||
"100%": { opacity: 1, transform: "translateX(0)" },
|
||||
});
|
||||
|
||||
const StyledContent = styled(TooltipPrimitive.Content, {
|
||||
borderRadius: 4,
|
||||
padding: "$2 $3",
|
||||
fontSize: 12,
|
||||
lineHeight: 1,
|
||||
color: "$text",
|
||||
backgroundColor: "$background",
|
||||
boxShadow:
|
||||
"hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px",
|
||||
"@media (prefers-reduced-motion: no-preference)": {
|
||||
animationDuration: "400ms",
|
||||
animationTimingFunction: "cubic-bezier(0.16, 1, 0.3, 1)",
|
||||
animationFillMode: "forwards",
|
||||
willChange: "transform, opacity",
|
||||
'&[data-state="delayed-open"]': {
|
||||
'&[data-side="top"]': { animationName: slideDownAndFade },
|
||||
'&[data-side="right"]': { animationName: slideLeftAndFade },
|
||||
'&[data-side="bottom"]': { animationName: slideUpAndFade },
|
||||
'&[data-side="left"]': { animationName: slideRightAndFade },
|
||||
},
|
||||
},
|
||||
".dark &": {
|
||||
boxShadow:
|
||||
"0px 0px 10px 2px rgba(255,255,255,.15), hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px",
|
||||
},
|
||||
".light &": {
|
||||
boxShadow:
|
||||
"0px 0px 10px 2px rgba(0,0,0,.15), hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px",
|
||||
},
|
||||
});
|
||||
|
||||
const StyledArrow = styled(TooltipPrimitive.Arrow, {
|
||||
fill: "$background",
|
||||
});
|
||||
|
||||
interface ITooltip {
|
||||
content: string;
|
||||
open?: boolean;
|
||||
defaultOpen?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
}
|
||||
|
||||
const Tooltip: React.FC<ITooltip> = ({
|
||||
children,
|
||||
content,
|
||||
open,
|
||||
defaultOpen = false,
|
||||
onOpenChange,
|
||||
}) => {
|
||||
return (
|
||||
<TooltipPrimitive.Root
|
||||
open={open}
|
||||
defaultOpen={defaultOpen}
|
||||
onOpenChange={onOpenChange}
|
||||
>
|
||||
<TooltipPrimitive.Trigger asChild>{children}</TooltipPrimitive.Trigger>
|
||||
<StyledContent side="bottom" align="center">
|
||||
{content}
|
||||
<StyledArrow offset={5} width={11} height={5} />
|
||||
</StyledContent>
|
||||
</TooltipPrimitive.Root>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tooltip;
|
||||
@@ -1,15 +1,17 @@
|
||||
export { default as Flex } from './Flex'
|
||||
export { default as Container } from './Container'
|
||||
export { default as Heading } from './Heading'
|
||||
export { default as Stack } from './Stack'
|
||||
export { default as Text } from './Text'
|
||||
export { default as Input } from './Input'
|
||||
export { default as Select } from './Select'
|
||||
export * from './Tabs'
|
||||
export * from './AlertDialog'
|
||||
export { default as Box } from './Box'
|
||||
export { default as Button } from './Button'
|
||||
export { default as ButtonGroup } from './ButtonGroup'
|
||||
export { default as DeployFooter } from './DeployFooter'
|
||||
export * from './Dialog'
|
||||
export * from './DropdownMenu'
|
||||
export { default as Flex } from "./Flex";
|
||||
export { default as Link } from "./Link";
|
||||
export { default as Container } from "./Container";
|
||||
export { default as Heading } from "./Heading";
|
||||
export { default as Stack } from "./Stack";
|
||||
export { default as Text } from "./Text";
|
||||
export { default as Input } from "./Input";
|
||||
export { default as Select } from "./Select";
|
||||
export * from "./Tabs";
|
||||
export * from "./AlertDialog";
|
||||
export { default as Box } from "./Box";
|
||||
export { default as Button } from "./Button";
|
||||
export { default as Pre } from "./Pre";
|
||||
export { default as ButtonGroup } from "./ButtonGroup";
|
||||
export { default as DeployFooter } from "./DeployFooter";
|
||||
export * from "./Dialog";
|
||||
export * from "./DropdownMenu";
|
||||
|
||||
1
next-env.d.ts
vendored
@@ -1,5 +1,4 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
|
||||
@@ -11,6 +11,10 @@ module.exports = {
|
||||
if (!isServer) {
|
||||
config.resolve.fallback.fs = false;
|
||||
}
|
||||
config.module.rules.push({
|
||||
test: /\.md$/,
|
||||
use: "raw-loader",
|
||||
});
|
||||
return config;
|
||||
},
|
||||
};
|
||||
|
||||
22
package.json
@@ -12,22 +12,27 @@
|
||||
"dependencies": {
|
||||
"@codingame/monaco-jsonrpc": "^0.3.1",
|
||||
"@codingame/monaco-languageclient": "^0.17.0",
|
||||
"@monaco-editor/react": "^4.3.1",
|
||||
"@monaco-editor/react": "^4.4.1",
|
||||
"@octokit/core": "^3.5.1",
|
||||
"@radix-ui/colors": "^0.1.7",
|
||||
"@radix-ui/react-alert-dialog": "^0.1.1",
|
||||
"@radix-ui/react-dialog": "^0.1.1",
|
||||
"@radix-ui/react-dropdown-menu": "^0.1.1",
|
||||
"@radix-ui/react-id": "^0.1.1",
|
||||
"@radix-ui/react-tooltip": "^0.1.7",
|
||||
"@stitches/react": "^1.2.6-0",
|
||||
"base64-js": "^1.5.1",
|
||||
"dinero.js": "^1.9.1",
|
||||
"file-saver": "^2.0.5",
|
||||
"filesize": "^8.0.7",
|
||||
"javascript-time-ago": "^2.3.11",
|
||||
"jszip": "^3.7.1",
|
||||
"monaco-editor": "^0.30.1",
|
||||
"lodash.uniqby": "^4.7.0",
|
||||
"lodash.xor": "^4.5.0",
|
||||
"monaco-editor": "^0.33.0",
|
||||
"next": "^12.0.4",
|
||||
"next-auth": "^4.0.0-beta.5",
|
||||
"next-themes": "^0.0.15",
|
||||
"next-themes": "^0.1.1",
|
||||
"normalize-url": "^7.0.2",
|
||||
"octokit": "^1.7.0",
|
||||
"pako": "^2.0.4",
|
||||
@@ -37,26 +42,33 @@
|
||||
"re-resizable": "^6.9.1",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-hook-form": "^7.28.0",
|
||||
"react-hot-keys": "^2.7.1",
|
||||
"react-hot-toast": "^2.1.1",
|
||||
"react-new-window": "^0.2.1",
|
||||
"react-select": "^5.2.1",
|
||||
"react-split": "^2.0.14",
|
||||
"react-stay-scrolled": "^7.4.0",
|
||||
"react-time-ago": "^7.1.9",
|
||||
"reconnecting-websocket": "^4.4.0",
|
||||
"regexify-string": "^1.0.17",
|
||||
"valtio": "^1.2.5",
|
||||
"vscode-languageserver": "^7.0.0",
|
||||
"vscode-uri": "^3.0.2",
|
||||
"wabt": "1.0.16",
|
||||
"xrpl-accountlib": "^1.2.3",
|
||||
"xrpl-client": "^1.9.3"
|
||||
"xrpl-accountlib": "^1.3.2",
|
||||
"xrpl-client": "^1.9.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/dinero.js": "^1.9.0",
|
||||
"@types/file-saver": "^2.0.4",
|
||||
"@types/lodash.uniqby": "^4.7.6",
|
||||
"@types/lodash.xor": "^4.5.6",
|
||||
"@types/pako": "^1.0.2",
|
||||
"@types/react": "17.0.31",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-config-next": "11.1.2",
|
||||
"raw-loader": "^4.0.2",
|
||||
"typescript": "4.4.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,25 +13,105 @@ import Navigation from "../components/Navigation";
|
||||
import { fetchFiles } from "../state/actions";
|
||||
import state from "../state";
|
||||
|
||||
import TimeAgo from "javascript-time-ago";
|
||||
import en from "javascript-time-ago/locale/en.json";
|
||||
import { useSnapshot } from "valtio";
|
||||
|
||||
TimeAgo.setDefaultLocale(en.locale);
|
||||
TimeAgo.addLocale(en);
|
||||
|
||||
function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
|
||||
const router = useRouter();
|
||||
const slug = router.query?.slug;
|
||||
const gistId = (Array.isArray(slug) && slug[0]) ?? null;
|
||||
|
||||
const origin = "https://xrpl-hooks-ide.vercel.app"; // TODO: Change when site is deployed
|
||||
const shareImg = "/share-image.png";
|
||||
|
||||
const snap = useSnapshot(state);
|
||||
useEffect(() => {
|
||||
if (gistId && router.isReady) {
|
||||
fetchFiles(gistId);
|
||||
} else {
|
||||
if (!gistId && router.isReady && !router.pathname.includes("/sign-in")) {
|
||||
if (
|
||||
!gistId &&
|
||||
router.isReady &&
|
||||
!router.pathname.includes("/sign-in") &&
|
||||
!snap.files.length &&
|
||||
!snap.mainModalShowed
|
||||
) {
|
||||
state.mainModalOpen = true;
|
||||
state.mainModalShowed = true;
|
||||
}
|
||||
}
|
||||
}, [gistId, router.isReady, router.pathname]);
|
||||
}, [
|
||||
gistId,
|
||||
router.isReady,
|
||||
router.pathname,
|
||||
snap.files,
|
||||
snap.mainModalShowed,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>XRPL Hooks Playground</title>
|
||||
<meta charSet="utf-8" />
|
||||
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<meta property="og:url" content={`${origin}${router.asPath}`} />
|
||||
|
||||
<title>XRPL Hooks Editor</title>
|
||||
<meta property="og:title" content="XRPL Hooks Editor" />
|
||||
<meta name="twitter:title" content="XRPL Hooks Editor" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:site" content="@xrpllabs" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Playground for buildings Hooks, that add smart contract functionality to the XRP Ledger."
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Playground for buildings Hooks, that add smart contract functionality to the XRP Ledger."
|
||||
/>
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Playground for buildings Hooks, that add smart contract functionality to the XRP Ledger.."
|
||||
/>
|
||||
<meta property="og:image" content={`${origin}${shareImg}`} />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta name="twitter:image" content={`${origin}${shareImg}`} />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#161618" />
|
||||
<meta name="application-name" content="XRPL Hooks Editor" />
|
||||
<meta name="msapplication-TileColor" content="#c10ad0" />
|
||||
<meta
|
||||
name="theme-color"
|
||||
content="#161618"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
/>
|
||||
<meta
|
||||
name="theme-color"
|
||||
content="#FDFCFD"
|
||||
media="(prefers-color-scheme: light)"
|
||||
/>
|
||||
</Head>
|
||||
<IdProvider>
|
||||
<SessionProvider session={session}>
|
||||
|
||||
@@ -16,11 +16,14 @@ class MyDocument extends Document {
|
||||
}
|
||||
render() {
|
||||
globalStyles();
|
||||
|
||||
return (
|
||||
<Html>
|
||||
<Head>
|
||||
<meta name="description" content="Playground for XRPL Hooks" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<style
|
||||
id="stitches"
|
||||
dangerouslySetInnerHTML={{ __html: getCssText() }}
|
||||
/>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link
|
||||
rel="preconnect"
|
||||
@@ -31,10 +34,6 @@ class MyDocument extends Document {
|
||||
href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital@0;1&family=Work+Sans:wght@400;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<style
|
||||
id="stitches"
|
||||
dangerouslySetInnerHTML={{ __html: getCssText() }}
|
||||
/>
|
||||
</Head>
|
||||
<body>
|
||||
<Main />
|
||||
|
||||
@@ -4,7 +4,9 @@ import { NextResponse as Response } from 'next/server';
|
||||
export default function middleware(req: NextRequest, ev: NextFetchEvent) {
|
||||
|
||||
if (req.nextUrl.pathname === "/") {
|
||||
return Response.redirect("/develop");
|
||||
const url = req.nextUrl.clone();
|
||||
url.pathname = '/develop';
|
||||
return Response.redirect(url);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,15 @@ export default async function handler(
|
||||
if (req.method !== 'POST') {
|
||||
return res.status(405).json({ error: 'Method not allowed!' })
|
||||
}
|
||||
const { account } = req.query;
|
||||
const ip = Array.isArray(req?.headers?.["x-real-ip"]) ? req?.headers?.["x-real-ip"][0] : req?.headers?.["x-real-ip"];
|
||||
try {
|
||||
const response = await fetch('https://hooks-testnet.xrpl-labs.com/newcreds', { method: 'POST' });
|
||||
const response = await fetch(`https://${process.env.NEXT_PUBLIC_TESTNET_URL}/newcreds?account=${account ? account : ''}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'x-forwarded-for': ip || '',
|
||||
},
|
||||
});
|
||||
const json: Faucet | ErrorResponse = await response.json();
|
||||
if ("error" in json) {
|
||||
return res.status(429).json(json)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import { Flex, Box } from "../../components";
|
||||
import React from "react";
|
||||
import Split from "react-split";
|
||||
import { useSnapshot } from "valtio";
|
||||
import state from "../../state";
|
||||
import { getSplit, saveSplit } from "../../state/actions/persistSplits";
|
||||
|
||||
const DeployEditor = dynamic(() => import("../../components/DeployEditor"), {
|
||||
ssr: false,
|
||||
@@ -17,25 +18,45 @@ const LogBox = dynamic(() => import("../../components/LogBox"), {
|
||||
});
|
||||
|
||||
const Deploy = () => {
|
||||
const snap = useSnapshot(state);
|
||||
const { deployLogs } = useSnapshot(state);
|
||||
return (
|
||||
<>
|
||||
<main style={{ display: "flex", flex: 1, height: 'calc(100vh - 30vh - 60px)' }}>
|
||||
<Split
|
||||
direction="vertical"
|
||||
gutterSize={4}
|
||||
gutterAlign="center"
|
||||
sizes={getSplit("deployVertical") || [40, 60]}
|
||||
style={{ height: "calc(100vh - 60px)" }}
|
||||
onDragEnd={(e) => saveSplit("deployVertical", e)}
|
||||
>
|
||||
<main style={{ display: "flex", flex: 1, position: "relative" }}>
|
||||
<DeployEditor />
|
||||
</main>
|
||||
<Flex css={{ flexDirection: "row", width: "100%", minHeight: '225px', height: '30vh' }}>
|
||||
<Box css={{ width: "100%" }}>
|
||||
<Split
|
||||
direction="horizontal"
|
||||
sizes={getSplit("deployHorizontal") || [50, 50]}
|
||||
minSize={[320, 160]}
|
||||
gutterSize={4}
|
||||
gutterAlign="center"
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
onDragEnd={(e) => saveSplit("deployHorizontal", e)}
|
||||
>
|
||||
<div style={{ alignItems: "stretch", display: "flex" }}>
|
||||
<Accounts />
|
||||
</Box>
|
||||
<Box css={{ width: "100%" }}>
|
||||
</div>
|
||||
<div>
|
||||
<LogBox
|
||||
title="Deploy Log"
|
||||
logs={snap.deployLogs}
|
||||
logs={deployLogs}
|
||||
clearLog={() => (state.deployLogs = [])}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
</>
|
||||
</div>
|
||||
</Split>
|
||||
</Split>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import dynamic from "next/dynamic";
|
||||
import { useSnapshot } from "valtio";
|
||||
import Hotkeys from "react-hot-keys";
|
||||
import { Play } from "phosphor-react";
|
||||
|
||||
import type { NextPage } from "next";
|
||||
import { compileCode } from "../../state/actions";
|
||||
import state from "../../state";
|
||||
import Button from "../../components/Button";
|
||||
import dynamic from "next/dynamic";
|
||||
import { Play } from "phosphor-react";
|
||||
import Hotkeys from "react-hot-keys";
|
||||
import Split from "react-split";
|
||||
import { useSnapshot } from "valtio";
|
||||
import Box from "../../components/Box";
|
||||
import Button from "../../components/Button";
|
||||
import state from "../../state";
|
||||
import { compileCode } from "../../state/actions";
|
||||
import { getSplit, saveSplit } from "../../state/actions/persistSplits";
|
||||
|
||||
|
||||
const HooksEditor = dynamic(() => import("../../components/HooksEditor"), {
|
||||
ssr: false,
|
||||
@@ -19,8 +21,17 @@ const LogBox = dynamic(() => import("../../components/LogBox"), {
|
||||
|
||||
const Home: NextPage = () => {
|
||||
const snap = useSnapshot(state);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Split
|
||||
direction="vertical"
|
||||
sizes={getSplit("developVertical") || [70, 30]}
|
||||
minSize={[100, 100]}
|
||||
gutterAlign="center"
|
||||
gutterSize={4}
|
||||
style={{ height: "calc(100vh - 60px)" }}
|
||||
onDragEnd={(e) => saveSplit("developVertical", e)}
|
||||
>
|
||||
<main style={{ display: "flex", flex: 1, position: "relative" }}>
|
||||
<HooksEditor />
|
||||
{snap.files[snap.active]?.name?.split(".")?.[1].toLowerCase() ===
|
||||
@@ -65,7 +76,7 @@ const Home: NextPage = () => {
|
||||
logs={snap.logs}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
</Split>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,11 +1,27 @@
|
||||
import { Container, Flex, Box, Tabs, Tab, Input, Select, Text, Button } from "../../components";
|
||||
import { Play } from "phosphor-react";
|
||||
import dynamic from "next/dynamic";
|
||||
import { Play } from "phosphor-react";
|
||||
import { FC, useCallback, useEffect, useState } from "react";
|
||||
import Split from "react-split";
|
||||
import { useSnapshot } from "valtio";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Container,
|
||||
Flex,
|
||||
Input,
|
||||
Select,
|
||||
Tab,
|
||||
Tabs,
|
||||
Text,
|
||||
} from "../../components";
|
||||
import transactionsData from "../../content/transactions.json";
|
||||
import state from "../../state";
|
||||
import { sendTransaction } from "../../state/actions";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import transactionsData from "../../content/transactions.json";
|
||||
import { getSplit, saveSplit } from "../../state/actions/persistSplits";
|
||||
|
||||
const DebugStream = dynamic(() => import("../../components/DebugStream"), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const LogBox = dynamic(() => import("../../components/LogBox"), {
|
||||
ssr: false,
|
||||
@@ -15,13 +31,20 @@ const Accounts = dynamic(() => import("../../components/Accounts"), {
|
||||
});
|
||||
|
||||
// type SelectOption<T> = { value: T, label: string };
|
||||
type TxFields = Omit<typeof transactionsData[0], "Account" | "Sequence" | "TransactionType">;
|
||||
type TxFields = Omit<
|
||||
typeof transactionsData[0],
|
||||
"Account" | "Sequence" | "TransactionType"
|
||||
>;
|
||||
type OtherFields = (keyof Omit<TxFields, "Destination">)[];
|
||||
|
||||
const Transaction = () => {
|
||||
interface Props {
|
||||
header?: string;
|
||||
}
|
||||
|
||||
const Transaction: FC<Props> = ({ header, ...props }) => {
|
||||
const snap = useSnapshot(state);
|
||||
|
||||
const transactionsOptions = transactionsData.map(tx => ({
|
||||
const transactionsOptions = transactionsData.map((tx) => ({
|
||||
value: tx.TransactionType,
|
||||
label: tx.TransactionType,
|
||||
}));
|
||||
@@ -29,18 +52,20 @@ const Transaction = () => {
|
||||
typeof transactionsOptions[0] | null
|
||||
>(null);
|
||||
|
||||
const accountOptions = snap.accounts.map(acc => ({
|
||||
const accountOptions = snap.accounts.map((acc) => ({
|
||||
label: acc.name,
|
||||
value: acc.address,
|
||||
}));
|
||||
const [selectedAccount, setSelectedAccount] = useState<typeof accountOptions[0] | null>(null);
|
||||
const [selectedAccount, setSelectedAccount] = useState<
|
||||
typeof accountOptions[0] | null
|
||||
>(null);
|
||||
|
||||
const destAccountOptions = snap.accounts
|
||||
.map(acc => ({
|
||||
.map((acc) => ({
|
||||
label: acc.name,
|
||||
value: acc.address,
|
||||
}))
|
||||
.filter(acc => acc.value !== selectedAccount?.value);
|
||||
.filter((acc) => acc.value !== selectedAccount?.value);
|
||||
const [selectedDestAccount, setSelectedDestAccount] = useState<
|
||||
typeof destAccountOptions[0] | null
|
||||
>(null);
|
||||
@@ -51,7 +76,9 @@ const Transaction = () => {
|
||||
|
||||
useEffect(() => {
|
||||
const transactionType = selectedTransaction?.value;
|
||||
const account = snap.accounts.find(acc => acc.address === selectedAccount?.value);
|
||||
const account = snap.accounts.find(
|
||||
(acc) => acc.address === selectedAccount?.value
|
||||
);
|
||||
if (!account || !transactionType || txIsLoading) {
|
||||
setTxIsDisabled(true);
|
||||
} else {
|
||||
@@ -61,7 +88,7 @@ const Transaction = () => {
|
||||
|
||||
useEffect(() => {
|
||||
let _txFields: TxFields | undefined = transactionsData.find(
|
||||
tx => tx.TransactionType === selectedTransaction?.value
|
||||
(tx) => tx.TransactionType === selectedTransaction?.value
|
||||
);
|
||||
if (!_txFields) return setTxFields({});
|
||||
_txFields = { ..._txFields } as TxFields;
|
||||
@@ -77,7 +104,9 @@ const Transaction = () => {
|
||||
}, [selectedTransaction, setSelectedDestAccount]);
|
||||
|
||||
const submitTest = useCallback(async () => {
|
||||
const account = snap.accounts.find(acc => acc.address === selectedAccount?.value);
|
||||
const account = snap.accounts.find(
|
||||
(acc) => acc.address === selectedAccount?.value
|
||||
);
|
||||
const TransactionType = selectedTransaction?.value;
|
||||
if (!account || !TransactionType || txIsDisabled) return;
|
||||
|
||||
@@ -87,7 +116,7 @@ const Transaction = () => {
|
||||
let options = { ...txFields };
|
||||
|
||||
options.Destination = selectedDestAccount?.value;
|
||||
(Object.keys(options) as (keyof TxFields)[]).forEach(field => {
|
||||
(Object.keys(options) as (keyof TxFields)[]).forEach((field) => {
|
||||
let _value = options[field];
|
||||
// convert currency
|
||||
if (typeof _value === "object" && _value.type === "currency") {
|
||||
@@ -118,10 +147,15 @@ const Transaction = () => {
|
||||
delete options[field];
|
||||
}
|
||||
});
|
||||
await sendTransaction(account, {
|
||||
TransactionType,
|
||||
...options,
|
||||
});
|
||||
const logPrefix = header ? `${header.split(".")[0]}: ` : undefined;
|
||||
await sendTransaction(
|
||||
account,
|
||||
{
|
||||
TransactionType,
|
||||
...options,
|
||||
},
|
||||
{ logPrefix }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
if (error instanceof Error) {
|
||||
@@ -130,9 +164,10 @@ const Transaction = () => {
|
||||
}
|
||||
setTxIsLoading(false);
|
||||
}, [
|
||||
selectedAccount,
|
||||
selectedDestAccount,
|
||||
selectedTransaction,
|
||||
header,
|
||||
selectedAccount?.value,
|
||||
selectedDestAccount?.value,
|
||||
selectedTransaction?.value,
|
||||
snap.accounts,
|
||||
txFields,
|
||||
txIsDisabled,
|
||||
@@ -148,12 +183,30 @@ const Transaction = () => {
|
||||
}, []);
|
||||
|
||||
const usualFields = ["TransactionType", "Amount", "Account", "Destination"];
|
||||
const otherFields = Object.keys(txFields).filter(k => !usualFields.includes(k)) as OtherFields;
|
||||
const otherFields = Object.keys(txFields).filter(
|
||||
(k) => !usualFields.includes(k)
|
||||
) as OtherFields;
|
||||
return (
|
||||
<Box css={{ position: "relative", height: "calc(100% - 28px)" }}>
|
||||
<Container css={{ p: "$3 0", fontSize: "$sm", height: "calc(100% - 28px)" }}>
|
||||
<Box css={{ position: "relative", height: "calc(100% - 28px)" }} {...props}>
|
||||
<Container
|
||||
css={{
|
||||
p: "$3 01",
|
||||
fontSize: "$sm",
|
||||
height: "calc(100% - 45px)",
|
||||
}}
|
||||
>
|
||||
<Flex column fluid css={{ height: "100%", overflowY: "auto" }}>
|
||||
<Flex row fluid css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}>
|
||||
<Flex
|
||||
row
|
||||
fluid
|
||||
css={{
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
mb: "$3",
|
||||
mt: "1px",
|
||||
pr: "1px",
|
||||
}}
|
||||
>
|
||||
<Text muted css={{ mr: "$3" }}>
|
||||
Transaction type:{" "}
|
||||
</Text>
|
||||
@@ -164,10 +217,19 @@ const Transaction = () => {
|
||||
hideSelectedOptions
|
||||
css={{ width: "70%" }}
|
||||
value={selectedTransaction}
|
||||
onChange={tt => setSelectedTransaction(tt as any)}
|
||||
onChange={(tt) => setSelectedTransaction(tt as any)}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex row fluid css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}>
|
||||
<Flex
|
||||
row
|
||||
fluid
|
||||
css={{
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
mb: "$3",
|
||||
pr: "1px",
|
||||
}}
|
||||
>
|
||||
<Text muted css={{ mr: "$3" }}>
|
||||
Account:{" "}
|
||||
</Text>
|
||||
@@ -177,29 +239,46 @@ const Transaction = () => {
|
||||
css={{ width: "70%" }}
|
||||
options={accountOptions}
|
||||
value={selectedAccount}
|
||||
onChange={acc => setSelectedAccount(acc as any)}
|
||||
onChange={(acc) => setSelectedAccount(acc as any)}
|
||||
/>
|
||||
</Flex>
|
||||
{txFields.Amount !== undefined && (
|
||||
<Flex row fluid css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}>
|
||||
<Flex
|
||||
row
|
||||
fluid
|
||||
css={{
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
mb: "$3",
|
||||
pr: "1px",
|
||||
}}
|
||||
>
|
||||
<Text muted css={{ mr: "$3" }}>
|
||||
Amount (XRP):{" "}
|
||||
</Text>
|
||||
<Input
|
||||
value={txFields.Amount.value}
|
||||
onChange={e =>
|
||||
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" }}>
|
||||
<Flex
|
||||
row
|
||||
fluid
|
||||
css={{
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
mb: "$3",
|
||||
pr: "1px",
|
||||
}}
|
||||
>
|
||||
<Text muted css={{ mr: "$3" }}>
|
||||
Destination account:{" "}
|
||||
</Text>
|
||||
@@ -210,28 +289,37 @@ const Transaction = () => {
|
||||
options={destAccountOptions}
|
||||
value={selectedDestAccount}
|
||||
isClearable
|
||||
onChange={acc => setSelectedDestAccount(acc as any)}
|
||||
onChange={(acc) => setSelectedDestAccount(acc as any)}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{otherFields.map(field => {
|
||||
{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";
|
||||
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" }}
|
||||
css={{
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
mb: "$3",
|
||||
pr: "1px",
|
||||
}}
|
||||
>
|
||||
<Text muted css={{ mr: "$3" }}>
|
||||
{field + (isCurrency ? " (XRP)" : "")}:{" "}
|
||||
</Text>
|
||||
<Input
|
||||
value={value}
|
||||
onChange={e =>
|
||||
onChange={(e) =>
|
||||
setTxFields({
|
||||
...txFields,
|
||||
[field]:
|
||||
@@ -240,7 +328,6 @@ const Transaction = () => {
|
||||
: e.target.value,
|
||||
})
|
||||
}
|
||||
variant="deep"
|
||||
css={{ width: "70%", flex: "inherit", height: "$9" }}
|
||||
/>
|
||||
</Flex>
|
||||
@@ -279,38 +366,97 @@ const Transaction = () => {
|
||||
};
|
||||
|
||||
const Test = () => {
|
||||
const snap = useSnapshot(state);
|
||||
const { transactionLogs } = useSnapshot(state);
|
||||
const [tabHeaders, setTabHeaders] = useState<string[]>(["test1.json"]);
|
||||
return (
|
||||
<Container css={{ py: "$3", px: 0 }}>
|
||||
<Flex row fluid css={{ justifyContent: 'center', mb: "$2", height: '40vh', minHeight: '300px', p: '$3 $2' }}>
|
||||
<Box css={{ width: "55%", px: "$2" }}>
|
||||
<Tabs>
|
||||
{/* TODO Dynamic tabs */}
|
||||
<Tab header="test1.json">
|
||||
<Transaction />
|
||||
</Tab>
|
||||
<Tab header="test2.json">
|
||||
<Transaction />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Box>
|
||||
<Box css={{ width: "45%", mx: "$2", height: '100%' }}>
|
||||
<Accounts card hideDeployBtn showHookStats />
|
||||
</Box>
|
||||
</Flex>
|
||||
<Container css={{ px: 0 }}>
|
||||
<Split
|
||||
direction="vertical"
|
||||
sizes={getSplit("testVertical") || [50, 50]}
|
||||
gutterSize={4}
|
||||
gutterAlign="center"
|
||||
style={{ height: "calc(100vh - 60px)" }}
|
||||
onDragEnd={(e) => saveSplit("testVertical", e)}
|
||||
>
|
||||
<Flex
|
||||
row
|
||||
fluid
|
||||
css={{
|
||||
justifyContent: "center",
|
||||
p: "$3 $2",
|
||||
}}
|
||||
>
|
||||
<Split
|
||||
direction="horizontal"
|
||||
sizes={getSplit("testHorizontal") || [50, 50]}
|
||||
minSize={[180, 320]}
|
||||
gutterSize={4}
|
||||
gutterAlign="center"
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
onDragEnd={(e) => saveSplit("testHorizontal", e)}
|
||||
>
|
||||
<Box css={{ width: "55%", px: "$2" }}>
|
||||
<Tabs
|
||||
keepAllAlive
|
||||
forceDefaultExtension
|
||||
defaultExtension=".json"
|
||||
onCreateNewTab={(name) =>
|
||||
setTabHeaders(tabHeaders.concat(name))
|
||||
}
|
||||
onCloseTab={(index) =>
|
||||
setTabHeaders(tabHeaders.filter((_, idx) => idx !== index))
|
||||
}
|
||||
>
|
||||
{tabHeaders.map((header) => (
|
||||
<Tab key={header} header={header}>
|
||||
<Transaction header={header} />
|
||||
</Tab>
|
||||
))}
|
||||
</Tabs>
|
||||
</Box>
|
||||
<Box css={{ width: "45%", mx: "$2", height: "100%" }}>
|
||||
<Accounts card hideDeployBtn showHookStats />
|
||||
</Box>
|
||||
</Split>
|
||||
</Flex>
|
||||
|
||||
<Flex row fluid css={{ borderBottom: "1px solid $mauve8" }}>
|
||||
<Box css={{ width: "50%", borderRight: "1px solid $mauve8" }}>
|
||||
<LogBox
|
||||
title="From Log"
|
||||
logs={snap.transactionLogs}
|
||||
clearLog={() => (state.transactionLogs = [])}
|
||||
/>
|
||||
</Box>
|
||||
<Box css={{ width: "50%" }}>
|
||||
<LogBox title="To Log" logs={[]} clearLog={() => {}} />
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex row fluid>
|
||||
<Split
|
||||
direction="horizontal"
|
||||
sizes={[50, 50]}
|
||||
minSize={[320, 160]}
|
||||
gutterSize={4}
|
||||
gutterAlign="center"
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
css={{
|
||||
borderRight: "1px solid $mauve8",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<LogBox
|
||||
title="Development Log"
|
||||
logs={transactionLogs}
|
||||
clearLog={() => (state.transactionLogs = [])}
|
||||
/>
|
||||
</Box>
|
||||
<Box css={{ height: "100%" }}>
|
||||
<DebugStream />
|
||||
</Box>
|
||||
</Split>
|
||||
</Flex>
|
||||
</Split>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,419 +0,0 @@
|
||||
diff --git a/node_modules/ripple-binary-codec/dist/enums/definitions.json b/node_modules/ripple-binary-codec/dist/enums/definitions.json
|
||||
index 2333c42..b8f8eab 100644
|
||||
--- a/node_modules/ripple-binary-codec/dist/enums/definitions.json
|
||||
+++ b/node_modules/ripple-binary-codec/dist/enums/definitions.json
|
||||
@@ -1,3 +1,4 @@
|
||||
+
|
||||
{
|
||||
"TYPES": {
|
||||
"Validation": 10003,
|
||||
@@ -40,9 +41,7 @@
|
||||
"Check": 67,
|
||||
"Nickname": 110,
|
||||
"Contract": 99,
|
||||
- "NFTokenPage": 80,
|
||||
- "NFTokenOffer": 55,
|
||||
- "NegativeUNL": 78
|
||||
+ "GeneratorMap": 103
|
||||
},
|
||||
"FIELDS": [
|
||||
[
|
||||
@@ -95,16 +94,6 @@
|
||||
"type": "UInt16"
|
||||
}
|
||||
],
|
||||
- [
|
||||
- "TransferFee",
|
||||
- {
|
||||
- "nth": 4,
|
||||
- "isVLEncoded": false,
|
||||
- "isSerialized": true,
|
||||
- "isSigningField": true,
|
||||
- "type": "UInt16"
|
||||
- }
|
||||
- ],
|
||||
[
|
||||
"Flags",
|
||||
{
|
||||
@@ -455,6 +444,16 @@
|
||||
"type": "UInt32"
|
||||
}
|
||||
],
|
||||
+ [
|
||||
+ "EmitGeneration",
|
||||
+ {
|
||||
+ "nth": 43,
|
||||
+ "isVLEncoded": false,
|
||||
+ "isSerialized": true,
|
||||
+ "isSigningField": true,
|
||||
+ "type": "UInt32"
|
||||
+ }
|
||||
+ ],
|
||||
[
|
||||
"IndexNext",
|
||||
{
|
||||
@@ -635,16 +634,6 @@
|
||||
"type": "Hash256"
|
||||
}
|
||||
],
|
||||
- [
|
||||
- "TokenID",
|
||||
- {
|
||||
- "nth": 10,
|
||||
- "isVLEncoded": false,
|
||||
- "isSerialized": true,
|
||||
- "isSigningField": true,
|
||||
- "type": "Hash256"
|
||||
- }
|
||||
- ],
|
||||
[
|
||||
"BookDirectory",
|
||||
{
|
||||
@@ -916,7 +905,7 @@
|
||||
}
|
||||
],
|
||||
[
|
||||
- "URI",
|
||||
+ "Generator",
|
||||
{
|
||||
"nth": 5,
|
||||
"isVLEncoded": true,
|
||||
@@ -1045,36 +1034,6 @@
|
||||
"type": "Blob"
|
||||
}
|
||||
],
|
||||
- [
|
||||
- "UNLModifyValidator",
|
||||
- {
|
||||
- "nth": 19,
|
||||
- "isVLEncoded": true,
|
||||
- "isSerialized": true,
|
||||
- "isSigningField": true,
|
||||
- "type": "Blob"
|
||||
- }
|
||||
- ],
|
||||
- [
|
||||
- "ValidatorToDisable",
|
||||
- {
|
||||
- "nth": 20,
|
||||
- "isVLEncoded": true,
|
||||
- "isSerialized": true,
|
||||
- "isSigningField": true,
|
||||
- "type": "Blob"
|
||||
- }
|
||||
- ],
|
||||
- [
|
||||
- "ValidatorToReEnable",
|
||||
- {
|
||||
- "nth": 21,
|
||||
- "isVLEncoded": true,
|
||||
- "isSerialized": true,
|
||||
- "isSigningField": true,
|
||||
- "type": "Blob"
|
||||
- }
|
||||
- ],
|
||||
[
|
||||
"Account",
|
||||
{
|
||||
@@ -1156,7 +1115,7 @@
|
||||
}
|
||||
],
|
||||
[
|
||||
- "Minter",
|
||||
+ "EmitCallback",
|
||||
{
|
||||
"nth": 9,
|
||||
"isVLEncoded": true,
|
||||
@@ -1276,9 +1235,9 @@
|
||||
}
|
||||
],
|
||||
[
|
||||
- "NonFungibleToken",
|
||||
+ "Signer",
|
||||
{
|
||||
- "nth": 12,
|
||||
+ "nth": 16,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
@@ -1286,9 +1245,9 @@
|
||||
}
|
||||
],
|
||||
[
|
||||
- "Signer",
|
||||
+ "Majority",
|
||||
{
|
||||
- "nth": 16,
|
||||
+ "nth": 18,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
@@ -1296,9 +1255,9 @@
|
||||
}
|
||||
],
|
||||
[
|
||||
- "Majority",
|
||||
+ "DisabledValidator",
|
||||
{
|
||||
- "nth": 18,
|
||||
+ "nth": 19,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
@@ -1306,9 +1265,9 @@
|
||||
}
|
||||
],
|
||||
[
|
||||
- "DisabledValidator",
|
||||
+ "EmitDetails",
|
||||
{
|
||||
- "nth": 19,
|
||||
+ "nth": 12,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
@@ -1395,16 +1354,6 @@
|
||||
"type": "STArray"
|
||||
}
|
||||
],
|
||||
- [
|
||||
- "NonFungibleTokens",
|
||||
- {
|
||||
- "nth": 10,
|
||||
- "isVLEncoded": false,
|
||||
- "isSerialized": true,
|
||||
- "isSigningField": true,
|
||||
- "type": "STArray"
|
||||
- }
|
||||
- ],
|
||||
[
|
||||
"Majorities",
|
||||
{
|
||||
@@ -1415,16 +1364,6 @@
|
||||
"type": "STArray"
|
||||
}
|
||||
],
|
||||
- [
|
||||
- "DisabledValidators",
|
||||
- {
|
||||
- "nth": 17,
|
||||
- "isVLEncoded": false,
|
||||
- "isSerialized": true,
|
||||
- "isSigningField": true,
|
||||
- "type": "STArray"
|
||||
- }
|
||||
- ],
|
||||
[
|
||||
"CloseResolution",
|
||||
{
|
||||
@@ -1535,16 +1474,6 @@
|
||||
"type": "Vector256"
|
||||
}
|
||||
],
|
||||
- [
|
||||
- "TokenIDs",
|
||||
- {
|
||||
- "nth": 4,
|
||||
- "isVLEncoded": true,
|
||||
- "isSerialized": true,
|
||||
- "isSigningField": true,
|
||||
- "type": "Vector256"
|
||||
- }
|
||||
- ],
|
||||
[
|
||||
"Transaction",
|
||||
{
|
||||
@@ -1596,7 +1525,7 @@
|
||||
}
|
||||
],
|
||||
[
|
||||
- "TicketCount",
|
||||
+ "HookStateCount",
|
||||
{
|
||||
"nth": 40,
|
||||
"isVLEncoded": false,
|
||||
@@ -1606,7 +1535,7 @@
|
||||
}
|
||||
],
|
||||
[
|
||||
- "TicketSequence",
|
||||
+ "HookReserveCount",
|
||||
{
|
||||
"nth": 41,
|
||||
"isVLEncoded": false,
|
||||
@@ -1616,7 +1545,7 @@
|
||||
}
|
||||
],
|
||||
[
|
||||
- "TokenTaxon",
|
||||
+ "HookDataMaxSize",
|
||||
{
|
||||
"nth": 42,
|
||||
"isVLEncoded": false,
|
||||
@@ -1626,23 +1555,23 @@
|
||||
}
|
||||
],
|
||||
[
|
||||
- "MintedTokens",
|
||||
+ "HookOn",
|
||||
{
|
||||
- "nth": 43,
|
||||
+ "nth": 16,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
- "type": "UInt32"
|
||||
+ "type": "UInt64"
|
||||
}
|
||||
],
|
||||
[
|
||||
- "BurnedTokens",
|
||||
+ "EmitBurden",
|
||||
{
|
||||
- "nth": 44,
|
||||
+ "nth": 12,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
- "type": "UInt32"
|
||||
+ "type": "UInt64"
|
||||
}
|
||||
],
|
||||
[
|
||||
@@ -1686,29 +1615,9 @@
|
||||
}
|
||||
],
|
||||
[
|
||||
- "PreviousPageMin",
|
||||
+ "EmitParentTxnID",
|
||||
{
|
||||
- "nth": 26,
|
||||
- "isVLEncoded": false,
|
||||
- "isSerialized": true,
|
||||
- "isSigningField": true,
|
||||
- "type": "Hash256"
|
||||
- }
|
||||
- ],
|
||||
- [
|
||||
- "NextPageMin",
|
||||
- {
|
||||
- "nth": 27,
|
||||
- "isVLEncoded": false,
|
||||
- "isSerialized": true,
|
||||
- "isSigningField": true,
|
||||
- "type": "Hash256"
|
||||
- }
|
||||
- ],
|
||||
- [
|
||||
- "BuyOffer",
|
||||
- {
|
||||
- "nth": 28,
|
||||
+ "nth": 10,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
@@ -1716,9 +1625,9 @@
|
||||
}
|
||||
],
|
||||
[
|
||||
- "SellOffer",
|
||||
+ "EmitNonce",
|
||||
{
|
||||
- "nth": 29,
|
||||
+ "nth": 11,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
@@ -1735,16 +1644,6 @@
|
||||
"type": "UInt8"
|
||||
}
|
||||
],
|
||||
- [
|
||||
- "UNLModifyDisabling",
|
||||
- {
|
||||
- "nth": 17,
|
||||
- "isVLEncoded": false,
|
||||
- "isSerialized": true,
|
||||
- "isSigningField": true,
|
||||
- "type": "UInt8"
|
||||
- }
|
||||
- ],
|
||||
[
|
||||
"DestinationNode",
|
||||
{
|
||||
@@ -1754,36 +1653,6 @@
|
||||
"isSigningField": true,
|
||||
"type": "UInt64"
|
||||
}
|
||||
- ],
|
||||
- [
|
||||
- "Cookie",
|
||||
- {
|
||||
- "nth": 10,
|
||||
- "isVLEncoded": false,
|
||||
- "isSerialized": true,
|
||||
- "isSigningField": true,
|
||||
- "type": "UInt64"
|
||||
- }
|
||||
- ],
|
||||
- [
|
||||
- "ServerVersion",
|
||||
- {
|
||||
- "nth": 11,
|
||||
- "isVLEncoded": false,
|
||||
- "isSerialized": true,
|
||||
- "isSigningField": true,
|
||||
- "type": "UInt64"
|
||||
- }
|
||||
- ],
|
||||
- [
|
||||
- "OfferNode",
|
||||
- {
|
||||
- "nth": 12,
|
||||
- "isVLEncoded": false,
|
||||
- "isSerialized": true,
|
||||
- "isSigningField": true,
|
||||
- "type": "UInt64"
|
||||
- }
|
||||
]
|
||||
],
|
||||
"TRANSACTION_RESULTS": {
|
||||
@@ -1908,18 +1777,7 @@
|
||||
"tecDUPLICATE": 149,
|
||||
"tecKILLED": 150,
|
||||
"tecHAS_OBLIGATIONS": 151,
|
||||
- "tecTOO_SOON": 152,
|
||||
-
|
||||
- "tecMAX_SEQUENCE_REACHED": 154,
|
||||
- "tecNO_SUITABLE_PAGE": 155,
|
||||
- "tecBUY_SELL_MISMATCH": 156,
|
||||
- "tecOFFER_TYPE_MISMATCH": 157,
|
||||
- "tecCANT_ACCEPT_OWN_OFFER": 158,
|
||||
- "tecINSUFFICIENT_FUNDS": 159,
|
||||
- "tecOBJECT_NOT_FOUND": 160,
|
||||
- "tecINSUFFICIENT_PAYMENT": 161,
|
||||
- "tecINCORRECT_ASSET": 162,
|
||||
- "tecTOO_MANY": 163
|
||||
+ "tecTOO_SOON": 152
|
||||
},
|
||||
"TRANSACTION_TYPES": {
|
||||
"Invalid": -1,
|
||||
@@ -1946,13 +1804,11 @@
|
||||
"DepositPreauth": 19,
|
||||
"TrustSet": 20,
|
||||
"AccountDelete": 21,
|
||||
- "NFTokenMint": 25,
|
||||
- "NFTokenBurn": 26,
|
||||
- "NFTokenCreateOffer": 27,
|
||||
- "NFTokenCancelOffer": 28,
|
||||
- "NFTokenAcceptOffer": 29,
|
||||
+ "SetHook": 22,
|
||||
+ "Invoke": 23,
|
||||
+ "Batch": 24,
|
||||
+
|
||||
"EnableAmendment": 100,
|
||||
- "SetFee": 101,
|
||||
- "UNLModify": 102
|
||||
+ "SetFee": 101
|
||||
}
|
||||
}
|
||||
1755
patches/ripple-binary-codec+1.3.2.patch
Normal file
BIN
public/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
public/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
public/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
9
public/browserconfig.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/mstile-150x150.png"/>
|
||||
<TileColor>#161618</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
BIN
public/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 737 B |
BIN
public/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 15 KiB |
BIN
public/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
18
public/safari-pinned-tab.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="588.000000pt" height="588.000000pt" viewBox="0 0 588.000000 588.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.14, written by Peter Selinger 2001-2017
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,588.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M3843 4097 c-381 -380 -737 -736 -791 -790 l-99 -99 -940 1 -940 0
|
||||
-204 -211 c-112 -116 -228 -235 -257 -265 -29 -30 -51 -57 -48 -60 2 -3 542
|
||||
-5 1198 -5 l1193 0 799 -799 799 -799 380 0 c209 0 378 2 376 5 -2 2 -422 423
|
||||
-933 934 l-928 929 921 920 c507 507 921 923 921 927 0 3 -170 5 -377 5 l-378
|
||||
0 -692 -693z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 837 B |
BIN
public/share-image.png
Normal file
|
After Width: | Height: | Size: 710 KiB |
19
public/site.webmanifest
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "Hooks Builder",
|
||||
"short_name": "Hooks Builder",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#161618",
|
||||
"background_color": "#161618",
|
||||
"display": "standalone"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
4
raw-loader.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
declare module "*.md" {
|
||||
const content: string;
|
||||
export default content;
|
||||
};
|
||||
@@ -29,8 +29,8 @@ export const names = [
|
||||
*/
|
||||
export const addFaucetAccount = async (showToast: boolean = false) => {
|
||||
// Lets limit the number of faucet accounts to 5 for now
|
||||
if (state.accounts.length > 4) {
|
||||
return toast.error("You can only have maximum 5 accounts");
|
||||
if (state.accounts.length > 5) {
|
||||
return toast.error("You can only have maximum 6 accounts");
|
||||
}
|
||||
if (typeof window !== 'undefined') {
|
||||
|
||||
@@ -50,14 +50,16 @@ export const addFaucetAccount = async (showToast: boolean = false) => {
|
||||
if (showToast) {
|
||||
toast.success("New account created", { id: toastId });
|
||||
}
|
||||
const currNames = state.accounts.map(acc => acc.name);
|
||||
state.accounts.push({
|
||||
name: names[state.accounts.length],
|
||||
name: names.filter(name => !currNames.includes(name))[0],
|
||||
xrp: (json.xrp || 0 * 1000000).toString(),
|
||||
address: json.address,
|
||||
secret: json.secret,
|
||||
sequence: 1,
|
||||
hooks: [],
|
||||
isLoading: false,
|
||||
version: '2'
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -66,11 +68,29 @@ export const addFaucetAccount = async (showToast: boolean = false) => {
|
||||
// fetch initial faucets
|
||||
(async function fetchFaucets() {
|
||||
if (typeof window !== 'undefined') {
|
||||
if (state.accounts.length < 2) {
|
||||
if (state.accounts.length === 0) {
|
||||
await addFaucetAccount();
|
||||
setTimeout(() => {
|
||||
addFaucetAccount();
|
||||
}, 10000);
|
||||
// setTimeout(() => {
|
||||
// addFaucetAccount();
|
||||
// }, 10000);
|
||||
}
|
||||
}
|
||||
})();
|
||||
})();
|
||||
|
||||
export const addFunds = async (address: string) => {
|
||||
const toastId = toast.loading("Requesting funds");
|
||||
const res = await fetch(`${window.location.origin}/api/faucet?account=${address}`, {
|
||||
method: "POST",
|
||||
});
|
||||
const json: FaucetAccountRes | { error: string } = await res.json();
|
||||
if ("error" in json) {
|
||||
return toast.error(json.error, { id: toastId });
|
||||
} else {
|
||||
toast.success(`Funds added (${json.xrp} XRP)`, { id: toastId });
|
||||
const currAccount = state.accounts.find(acc => acc.address === address);
|
||||
if (currAccount) {
|
||||
currAccount.xrp = (Number(currAccount.xrp) + (json.xrp * 1000000)).toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,6 +25,7 @@ export const compileCode = async (activeId: number) => {
|
||||
}
|
||||
// Set loading state to true
|
||||
state.compiling = true;
|
||||
state.logs = []
|
||||
try {
|
||||
const res = await fetch(process.env.NEXT_PUBLIC_COMPILE_API_ENDPOINT, {
|
||||
method: "POST",
|
||||
@@ -38,7 +39,6 @@ export const compileCode = async (activeId: number) => {
|
||||
{
|
||||
type: "c",
|
||||
name: state.files[activeId].name,
|
||||
options: "-O0",
|
||||
src: state.files[activeId].content,
|
||||
},
|
||||
],
|
||||
@@ -66,6 +66,7 @@ export const compileCode = async (activeId: number) => {
|
||||
// Decode base64 encoded wasm that is coming back from the endpoint
|
||||
const bufferData = await decodeBinary(json.output);
|
||||
state.files[state.active].compiledContent = ref(bufferData);
|
||||
state.files[state.active].lastCompiled = new Date();
|
||||
// Import wabt from and create human readable version of wasm file and
|
||||
// put it into state
|
||||
import("wabt").then((wabt) => {
|
||||
@@ -87,4 +88,4 @@ export const compileCode = async (activeId: number) => {
|
||||
});
|
||||
state.compiling = false;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import state, { IFile } from '../index';
|
||||
|
||||
/* Initializes empty file to global state */
|
||||
const languageMapping = {
|
||||
'ts': 'typescript',
|
||||
'js': 'javascript',
|
||||
'md': 'markdown',
|
||||
'c': 'c',
|
||||
'h': 'c',
|
||||
'other': ''
|
||||
} /* Initializes empty file to global state */
|
||||
export const createNewFile = (name: string) => {
|
||||
const emptyFile: IFile = { name, language: "c", content: "" };
|
||||
const tempName = name.split('.');
|
||||
const fileExt = tempName[tempName.length - 1] || 'other';
|
||||
const emptyFile: IFile = { name, language: languageMapping[fileExt as 'ts' | 'js' | 'md' | 'c' | 'h' | 'other'], content: "" };
|
||||
state.files.push(emptyFile);
|
||||
state.active = state.files.length - 1;
|
||||
};
|
||||
@@ -1,6 +1,27 @@
|
||||
import { derive, sign } from "xrpl-accountlib";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
import state, { IAccount } from "../index";
|
||||
import calculateHookOn, { TTS } from "../../utils/hookOnCalculator";
|
||||
import { SetHookData } from "../../components/SetHookDialog";
|
||||
|
||||
const hash = async (string: string) => {
|
||||
const utf8 = new TextEncoder().encode(string);
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', utf8);
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||
const hashHex = hashArray
|
||||
.map((bytes) => bytes.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
return hashHex;
|
||||
}
|
||||
|
||||
function toHex(str: string) {
|
||||
var result = '';
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
result += str.charCodeAt(i).toString(16);
|
||||
}
|
||||
return result.toUpperCase();
|
||||
}
|
||||
|
||||
function arrayBufferToHex(arrayBuffer?: ArrayBuffer | null) {
|
||||
if (!arrayBuffer) {
|
||||
@@ -30,7 +51,7 @@ function arrayBufferToHex(arrayBuffer?: ArrayBuffer | null) {
|
||||
* hex string, signs the transaction and deploys it to
|
||||
* Hooks testnet.
|
||||
*/
|
||||
export const deployHook = async (account: IAccount & { name?: string }) => {
|
||||
export const deployHook = async (account: IAccount & { name?: string }, data: SetHookData) => {
|
||||
if (
|
||||
!state.files ||
|
||||
state.files.length === 0 ||
|
||||
@@ -45,17 +66,45 @@ export const deployHook = async (account: IAccount & { name?: string }) => {
|
||||
if (!state.client) {
|
||||
return;
|
||||
}
|
||||
const HookNamespace = await hash(arrayBufferToHex(
|
||||
state.files?.[state.active]?.compiledContent
|
||||
).toUpperCase());
|
||||
const hookOnValues: (keyof TTS)[] = data.Invoke.map(tt => tt.value);
|
||||
const { HookParameters } = data;
|
||||
const filteredHookParameters = HookParameters.filter(hp => hp.HookParameter.HookParameterName && hp.HookParameter.HookParameterValue)?.map(aa => ({ HookParameter: { HookParameterName: toHex(aa.HookParameter.HookParameterName || ''), HookParameterValue: toHex(aa.HookParameter.HookParameterValue || '') } }));
|
||||
// const filteredHookGrants = HookGrants.filter(hg => hg.HookGrant.Authorize || hg.HookGrant.HookHash).map(hg => {
|
||||
// return {
|
||||
// HookGrant: {
|
||||
// ...(hg.HookGrant.Authorize && { Authorize: hg.HookGrant.Authorize }),
|
||||
// // HookHash: hg.HookGrant.HookHash || undefined
|
||||
// ...(hg.HookGrant.HookHash && { HookHash: hg.HookGrant.HookHash })
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
const tx = {
|
||||
Account: account.address,
|
||||
TransactionType: "SetHook",
|
||||
CreateCode: arrayBufferToHex(
|
||||
state.files?.[state.active]?.compiledContent
|
||||
).toUpperCase(),
|
||||
HookOn: "0000000000000000",
|
||||
Sequence: account.sequence,
|
||||
Fee: "1000",
|
||||
Fee: "100000",
|
||||
Hooks: [
|
||||
{
|
||||
Hook: {
|
||||
CreateCode: arrayBufferToHex(
|
||||
state.files?.[state.active]?.compiledContent
|
||||
).toUpperCase(),
|
||||
HookOn: calculateHookOn(hookOnValues),
|
||||
HookNamespace,
|
||||
HookApiVersion: 0,
|
||||
Flags: 1,
|
||||
// ...(filteredHookGrants.length > 0 && { HookGrants: filteredHookGrants }),
|
||||
...(filteredHookParameters.length > 0 && { HookParameters: filteredHookParameters }),
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const keypair = derive.familySeed(account.secret);
|
||||
const { signedTransaction } = sign(tx, keypair);
|
||||
const currentAccount = state.accounts.find(
|
||||
@@ -64,11 +113,13 @@ export const deployHook = async (account: IAccount & { name?: string }) => {
|
||||
if (currentAccount) {
|
||||
currentAccount.isLoading = true;
|
||||
}
|
||||
let submitRes;
|
||||
try {
|
||||
const submitRes = await state.client.send({
|
||||
submitRes = await state.client.send({
|
||||
command: "submit",
|
||||
tx_blob: signedTransaction,
|
||||
});
|
||||
|
||||
if (submitRes.engine_result === "tesSUCCESS") {
|
||||
state.deployLogs.push({
|
||||
type: "success",
|
||||
@@ -81,7 +132,7 @@ export const deployHook = async (account: IAccount & { name?: string }) => {
|
||||
} else {
|
||||
state.deployLogs.push({
|
||||
type: "error",
|
||||
message: `[${submitRes.engine_result}] ${submitRes.engine_result_message}`,
|
||||
message: `[${submitRes.engine_result || submitRes.error}] ${submitRes.engine_result_message || submitRes.error_exception}`,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -94,5 +145,79 @@ export const deployHook = async (account: IAccount & { name?: string }) => {
|
||||
if (currentAccount) {
|
||||
currentAccount.isLoading = false;
|
||||
}
|
||||
return submitRes;
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteHook = async (account: IAccount & { name?: string }) => {
|
||||
if (!state.client) {
|
||||
return;
|
||||
}
|
||||
const currentAccount = state.accounts.find(
|
||||
(acc) => acc.address === account.address
|
||||
);
|
||||
if (currentAccount?.isLoading || !currentAccount?.hooks.length) {
|
||||
return
|
||||
}
|
||||
if (typeof window !== "undefined") {
|
||||
const tx = {
|
||||
Account: account.address,
|
||||
TransactionType: "SetHook",
|
||||
Sequence: account.sequence,
|
||||
Fee: "100000",
|
||||
Hooks: [
|
||||
{
|
||||
Hook: {
|
||||
CreateCode: "",
|
||||
Flags: 1,
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const keypair = derive.familySeed(account.secret);
|
||||
const { signedTransaction } = sign(tx, keypair);
|
||||
|
||||
if (currentAccount) {
|
||||
currentAccount.isLoading = true;
|
||||
}
|
||||
let submitRes;
|
||||
const toastId = toast.loading("Deleting hook...");
|
||||
try {
|
||||
submitRes = await state.client.send({
|
||||
command: "submit",
|
||||
tx_blob: signedTransaction,
|
||||
});
|
||||
|
||||
if (submitRes.engine_result === "tesSUCCESS") {
|
||||
toast.success('Hook deleted successfully ✅', { id: toastId })
|
||||
state.deployLogs.push({
|
||||
type: "success",
|
||||
message: "Hook deleted successfully ✅",
|
||||
});
|
||||
state.deployLogs.push({
|
||||
type: "success",
|
||||
message: `[${submitRes.engine_result}] ${submitRes.engine_result_message} Validated ledger index: ${submitRes.validated_ledger_index}`,
|
||||
});
|
||||
currentAccount.hooks = [];
|
||||
} else {
|
||||
toast.error(`${submitRes.engine_result_message || submitRes.error_exception}`, { id: toastId })
|
||||
state.deployLogs.push({
|
||||
type: "error",
|
||||
message: `[${submitRes.engine_result || submitRes.error}] ${submitRes.engine_result_message || submitRes.error_exception}`,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
toast.error('Error occured while deleting hoook', { id: toastId })
|
||||
state.deployLogs.push({
|
||||
type: "error",
|
||||
message: "Error occured while deleting hook",
|
||||
});
|
||||
}
|
||||
if (currentAccount) {
|
||||
currentAccount.isLoading = false;
|
||||
}
|
||||
return submitRes;
|
||||
}
|
||||
};
|
||||
@@ -3,7 +3,6 @@ import Router from "next/router";
|
||||
import state from '../index';
|
||||
import { templateFileIds } from '../constants';
|
||||
|
||||
|
||||
const octokit = new Octokit();
|
||||
|
||||
/* Fetches Gist files from Githug Gists based on
|
||||
@@ -19,21 +18,42 @@ export const fetchFiles = (gistId: string) => {
|
||||
|
||||
octokit
|
||||
.request("GET /gists/{gist_id}", { gist_id: gistId })
|
||||
.then(res => {
|
||||
.then(async res => {
|
||||
if (!Object.values(templateFileIds).includes(gistId)) {
|
||||
return res
|
||||
}
|
||||
// in case of templates, fetch header file(s) and append to res
|
||||
return octokit.request("GET /gists/{gist_id}", { gist_id: templateFileIds.headers }).then(({ data: { files: headerFiles } }) => {
|
||||
const files = { ...res.data.files, ...headerFiles }
|
||||
res.data.files = files
|
||||
return res
|
||||
})
|
||||
let resHeaderJson;
|
||||
try {
|
||||
const resHeader = await fetch(`${process.env.NEXT_PUBLIC_COMPILE_API_BASE_URL}/api/header-files`);
|
||||
if (resHeader.ok) {
|
||||
resHeaderJson = await resHeader.json();
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
|
||||
const files = {
|
||||
...res.data.files,
|
||||
'hookapi.h': res.data.files?.['hookapi.h'] || { filename: 'hookapi.h', content: resHeaderJson.hookapi, language: 'C' },
|
||||
'hookmacro.h': res.data.files?.['hookmacro.h'] || { filename: 'hookmacro.h', content: resHeaderJson.hookmacro, language: 'C' },
|
||||
'sfcodes.h': res.data.files?.['sfcodes.h'] || { filename: 'sfcodes.h', content: resHeaderJson.sfcodes, language: 'C' },
|
||||
};
|
||||
res.data.files = files;
|
||||
|
||||
return res;
|
||||
// If you want to load templates from GIST instad, uncomment the code below and comment the code above.
|
||||
// return octokit.request("GET /gists/{gist_id}", { gist_id: templateFileIds.headers }).then(({ data: { files: headerFiles } }) => {
|
||||
// const files = { ...res.data.files, ...headerFiles }
|
||||
// console.log(headerFiles)
|
||||
// res.data.files = files
|
||||
// return res
|
||||
// })
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.data.files && Object.keys(res.data.files).length > 0) {
|
||||
const files = Object.keys(res.data.files).map((filename) => ({
|
||||
name: res.data.files?.[filename]?.filename || "noname.c",
|
||||
name: res.data.files?.[filename]?.filename || "untitled.c",
|
||||
language: res.data.files?.[filename]?.language?.toLowerCase() || "",
|
||||
content: res.data.files?.[filename]?.content || "",
|
||||
}));
|
||||
|
||||
@@ -24,6 +24,7 @@ export const importAccount = (secret: string) => {
|
||||
sequence: 1,
|
||||
hooks: [],
|
||||
isLoading: false,
|
||||
version: '2'
|
||||
});
|
||||
return toast.success("Account imported successfully!");
|
||||
};
|
||||
15
state/actions/persistSplits.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { snapshot } from "valtio"
|
||||
import state from ".."
|
||||
|
||||
export type SplitSize = number[]
|
||||
|
||||
export const saveSplit = (splitId: string, event: SplitSize) => {
|
||||
state.splits[splitId] = event
|
||||
}
|
||||
|
||||
export const getSplit = (splitId: string): SplitSize | null => {
|
||||
const { splits } = snapshot(state)
|
||||
const split = splits[splitId]
|
||||
return split ? split : null
|
||||
}
|
||||
|
||||
@@ -4,8 +4,9 @@ import state from '../index';
|
||||
// Saves the current editor content to global state
|
||||
export const saveFile = (showToast: boolean = true) => {
|
||||
const editorModels = state.editorCtx?.getModels();
|
||||
const sought = '/' + state.files[state.active].name;
|
||||
const currentModel = editorModels?.find((editorModel) => {
|
||||
return editorModel.uri.path === `/c/${state.files[state.active].name}`;
|
||||
return editorModel.uri.path.endsWith(sought);
|
||||
});
|
||||
if (state.files.length > 0) {
|
||||
state.files[state.active].content = currentModel?.getValue() || "";
|
||||
@@ -13,4 +14,4 @@ export const saveFile = (showToast: boolean = true) => {
|
||||
if (showToast) {
|
||||
toast.success("Saved successfully", { position: "bottom-center" });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -10,8 +10,11 @@ interface TransactionOptions {
|
||||
Destination?: string
|
||||
[index: string]: any
|
||||
}
|
||||
interface OtherOptions {
|
||||
logPrefix?: string
|
||||
}
|
||||
|
||||
export const sendTransaction = async (account: IAccount, txOptions: TransactionOptions) => {
|
||||
export const sendTransaction = async (account: IAccount, txOptions: TransactionOptions, options?: OtherOptions) => {
|
||||
if (!state.client) throw Error('XRPL client not initalized')
|
||||
|
||||
const { Fee = "1000", ...opts } = txOptions
|
||||
@@ -21,7 +24,11 @@ export const sendTransaction = async (account: IAccount, txOptions: TransactionO
|
||||
Fee, // TODO auto-fillable
|
||||
...opts
|
||||
};
|
||||
console.log({ tx });
|
||||
const currAcc = state.accounts.find(acc => acc.address === account.address);
|
||||
if (currAcc) {
|
||||
currAcc.sequence = account.sequence + 1;
|
||||
}
|
||||
const { logPrefix = '' } = options || {}
|
||||
try {
|
||||
const signedAccount = derive.familySeed(account.secret);
|
||||
const { signedTransaction } = sign(tx, signedAccount);
|
||||
@@ -32,19 +39,19 @@ export const sendTransaction = async (account: IAccount, txOptions: TransactionO
|
||||
if (response.engine_result === "tesSUCCESS") {
|
||||
state.transactionLogs.push({
|
||||
type: 'success',
|
||||
message: `Transaction success [${response.engine_result}]: ${response.engine_result_message}`
|
||||
message: `${logPrefix}[${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}`,
|
||||
message: `${logPrefix}[${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',
|
||||
message: err instanceof Error ? `${logPrefix}Error: ${err.message}` : `${logPrefix}Something went wrong, try again later`,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,11 +1,20 @@
|
||||
// export const templateFileIds = {
|
||||
// 'starter': '1d14e51e2e02dc0a508cb0733767a914', // TODO currently same as accept
|
||||
// 'firewall': 'bcd6d0c0fcbe52545ddb802481ff9d26',
|
||||
// 'notary': 'a789c75f591eeab7932fd702ed8cf9ea',
|
||||
// 'carbon': '43925143fa19735d8c6505c34d3a6a47',
|
||||
// 'peggy': 'ceaf352e2a65741341033ab7ef05c448',
|
||||
// 'headers': '9b448e8a55fab11ef5d1274cb59f9cf3'
|
||||
// }
|
||||
|
||||
export const templateFileIds = {
|
||||
'starter': '1d14e51e2e02dc0a508cb0733767a914', // TODO currently same as accept
|
||||
'accept': '1d14e51e2e02dc0a508cb0733767a914',
|
||||
'firewall': 'bcd6d0c0fcbe52545ddb802481ff9d26',
|
||||
'notary': 'a789c75f591eeab7932fd702ed8cf9ea',
|
||||
'carbon': '43925143fa19735d8c6505c34d3a6a47',
|
||||
'peggy': 'ceaf352e2a65741341033ab7ef05c448',
|
||||
'headers': '9b448e8a55fab11ef5d1274cb59f9cf3'
|
||||
'starter': '1f7d2963d9e342ea092286115274f3e3',
|
||||
'firewall': '70edec690f0de4dd315fad1f4f996d8c',
|
||||
'notary': '3d5677768fe8a54c4f6317e185d9ba66',
|
||||
'carbon': 'a9fbcaf1b816b198c7fc0f62962bebf2',
|
||||
'doubler': '56b86174aeb70b2b48eee962bad3e355',
|
||||
'peggy': 'd21298a37e1550b781682014762a567b',
|
||||
'headers': '55f639bce59a49c58c45e663776b5138'
|
||||
}
|
||||
|
||||
export const apiHeaderFiles = ['hookapi.h', 'sfcodes.h', 'hookmacro.h']
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { proxy, ref, subscribe } from "valtio";
|
||||
import { devtools } from 'valtio/utils'
|
||||
import type monaco from "monaco-editor";
|
||||
import { proxy, ref, subscribe } from "valtio";
|
||||
import { devtools } from 'valtio/utils';
|
||||
import { XrplClient } from "xrpl-client";
|
||||
import { SplitSize } from "./actions/persistSplits";
|
||||
|
||||
declare module "valtio" {
|
||||
function useSnapshot<T extends object>(p: T): T;
|
||||
function snapshot<T extends object>(p: T): T;
|
||||
}
|
||||
|
||||
export interface IFile {
|
||||
name: string;
|
||||
@@ -9,6 +15,7 @@ export interface IFile {
|
||||
content: string;
|
||||
compiledContent?: ArrayBuffer | null;
|
||||
compiledWatContent?: string | null;
|
||||
lastCompiled?: Date
|
||||
}
|
||||
|
||||
export interface FaucetAccountRes {
|
||||
@@ -27,13 +34,17 @@ export interface IAccount {
|
||||
sequence: number;
|
||||
hooks: string[];
|
||||
isLoading: boolean;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
export interface ILog {
|
||||
type: "error" | "warning" | "log" | "success";
|
||||
message: string;
|
||||
jsonData?: any,
|
||||
timestamp?: string;
|
||||
link?: string;
|
||||
linkText?: string;
|
||||
defaultCollapsed?: boolean
|
||||
}
|
||||
|
||||
export interface IState {
|
||||
@@ -54,9 +65,13 @@ export interface IState {
|
||||
editorSettings: {
|
||||
tabSize: number;
|
||||
};
|
||||
splits: {
|
||||
[id: string]: SplitSize
|
||||
};
|
||||
client: XrplClient | null;
|
||||
clientStatus: "offline" | "online";
|
||||
mainModalOpen: boolean;
|
||||
mainModalShowed: boolean;
|
||||
accounts: IAccount[];
|
||||
}
|
||||
|
||||
@@ -81,14 +96,19 @@ let initialState: IState = {
|
||||
editorSettings: {
|
||||
tabSize: 2,
|
||||
},
|
||||
splits: {},
|
||||
client: null,
|
||||
clientStatus: "offline" as "offline",
|
||||
mainModalOpen: false,
|
||||
mainModalShowed: false,
|
||||
accounts: [],
|
||||
};
|
||||
|
||||
let localStorageAccounts: string | null = null;
|
||||
let initialAccounts: IAccount[] = [];
|
||||
|
||||
// TODO: What exactly should we store in localStorage? editorSettings, splits, accounts?
|
||||
|
||||
// Check if there's a persited accounts in localStorage
|
||||
if (typeof window !== "undefined") {
|
||||
try {
|
||||
@@ -100,6 +120,8 @@ if (typeof window !== "undefined") {
|
||||
if (localStorageAccounts) {
|
||||
initialAccounts = JSON.parse(localStorageAccounts);
|
||||
}
|
||||
// filter out old accounts (they do not have version property at all)
|
||||
// initialAccounts = initialAccounts.filter(acc => acc.version === '2');
|
||||
}
|
||||
|
||||
// Initialize state
|
||||
@@ -108,9 +130,8 @@ const state = proxy<IState>({
|
||||
accounts: initialAccounts.length > 0 ? initialAccounts : [],
|
||||
logs: [],
|
||||
});
|
||||
|
||||
// Initialize socket connection
|
||||
const client = new XrplClient("wss://hooks-testnet.xrpl-labs.com");
|
||||
const client = new XrplClient(`wss://${process.env.NEXT_PUBLIC_TESTNET_URL}`);
|
||||
|
||||
client.on("online", () => {
|
||||
state.client = ref(client);
|
||||
@@ -137,4 +158,4 @@ if (typeof window !== "undefined") {
|
||||
}
|
||||
});
|
||||
}
|
||||
export default state
|
||||
export default state
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
// stitches.config.ts
|
||||
import type Stitches from '@stitches/react';
|
||||
import { createStitches } from '@stitches/react';
|
||||
import type Stitches from "@stitches/react";
|
||||
import { createStitches } from "@stitches/react";
|
||||
|
||||
import {
|
||||
gray,
|
||||
blue,
|
||||
red,
|
||||
green,
|
||||
plum,
|
||||
crimson,
|
||||
grass,
|
||||
slate,
|
||||
mauve,
|
||||
pink,
|
||||
yellow,
|
||||
amber,
|
||||
purple,
|
||||
grayDark,
|
||||
blueDark,
|
||||
redDark,
|
||||
greenDark,
|
||||
plumDark,
|
||||
crimsonDark,
|
||||
grassDark,
|
||||
slateDark,
|
||||
mauveDark,
|
||||
pinkDark,
|
||||
yellowDark,
|
||||
amberDark,
|
||||
purpleDark,
|
||||
} from '@radix-ui/colors';
|
||||
red,
|
||||
redDark,
|
||||
} from "@radix-ui/colors";
|
||||
|
||||
export const {
|
||||
styled,
|
||||
@@ -38,25 +37,30 @@ export const {
|
||||
colors: {
|
||||
...gray,
|
||||
...blue,
|
||||
...red,
|
||||
...green,
|
||||
...plum,
|
||||
...crimson,
|
||||
...grass,
|
||||
...slate,
|
||||
...mauve,
|
||||
...pink,
|
||||
...yellow,
|
||||
...amber,
|
||||
...purple,
|
||||
...red,
|
||||
accent: "#9D2DFF",
|
||||
background: "$gray1",
|
||||
backgroundAlt: "$gray4",
|
||||
text: "$gray12",
|
||||
textMuted: "$gray10",
|
||||
primary: "$plum",
|
||||
error: '$red9',
|
||||
warning: '$amber11',
|
||||
success: "$grass11",
|
||||
white: "white",
|
||||
black: "black",
|
||||
'deep': 'rgb(244, 244, 244)'
|
||||
deep: "rgb(244, 244, 244)",
|
||||
},
|
||||
fonts: {
|
||||
body: 'Work Sans, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif',
|
||||
heading: 'Work Sans, sans-serif',
|
||||
monospace: 'Roboto Mono, monospace',
|
||||
heading: "Work Sans, sans-serif",
|
||||
monospace: "Roboto Mono, monospace",
|
||||
},
|
||||
fontSizes: {
|
||||
xs: "0.6875rem",
|
||||
@@ -72,7 +76,7 @@ export const {
|
||||
"7xl": "4.5rem",
|
||||
"8xl": "6rem",
|
||||
"9xl": "8rem",
|
||||
default: '$md'
|
||||
default: "$md",
|
||||
},
|
||||
space: {
|
||||
px: "1px",
|
||||
@@ -108,15 +112,15 @@ export const {
|
||||
72: "18rem",
|
||||
80: "20rem",
|
||||
96: "24rem",
|
||||
"widePlus": '2048px',
|
||||
"wide": '1536px',
|
||||
"layoutPlus": '1260px',
|
||||
"layout": '1024px',
|
||||
"copyUltra": '980px',
|
||||
"copyPlus": '768px',
|
||||
"copy": '680px',
|
||||
"narrowPlus": '600px',
|
||||
"narrow": '512px',
|
||||
widePlus: "2048px",
|
||||
wide: "1536px",
|
||||
layoutPlus: "1260px",
|
||||
layout: "1024px",
|
||||
copyUltra: "980px",
|
||||
copyPlus: "768px",
|
||||
copy: "680px",
|
||||
narrowPlus: "600px",
|
||||
narrow: "512px",
|
||||
xs: "20rem",
|
||||
sm: "24rem",
|
||||
md: "28rem",
|
||||
@@ -216,62 +220,112 @@ export const {
|
||||
lg: "(min-width: 62em)",
|
||||
xl: "(min-width: 80em)",
|
||||
"2xl": "(min-width: 96em)",
|
||||
hover: '(any-hover: hover)',
|
||||
dark: '(prefers-color-scheme: dark)',
|
||||
light: '(prefers-color-scheme: light)',
|
||||
hover: "(any-hover: hover)",
|
||||
dark: "(prefers-color-scheme: dark)",
|
||||
light: "(prefers-color-scheme: light)",
|
||||
},
|
||||
utils: {
|
||||
// Abbreviated margin properties
|
||||
m: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'margin'>) => ({
|
||||
m: (
|
||||
value: Stitches.ScaleValue<"space"> | Stitches.PropertyValue<"margin">
|
||||
) => ({
|
||||
margin: value,
|
||||
}),
|
||||
mt: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'marginTop'>) => ({
|
||||
mt: (
|
||||
value: Stitches.ScaleValue<"space"> | Stitches.PropertyValue<"marginTop">
|
||||
) => ({
|
||||
marginTop: value,
|
||||
}),
|
||||
mr: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'marginRight'>) => ({
|
||||
mr: (
|
||||
value:
|
||||
| Stitches.ScaleValue<"space">
|
||||
| Stitches.PropertyValue<"marginRight">
|
||||
) => ({
|
||||
marginRight: value,
|
||||
}),
|
||||
mb: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'marginBottom'>) => ({
|
||||
mb: (
|
||||
value:
|
||||
| Stitches.ScaleValue<"space">
|
||||
| Stitches.PropertyValue<"marginBottom">
|
||||
) => ({
|
||||
marginBottom: value,
|
||||
}),
|
||||
ml: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'marginLeft'>) => ({
|
||||
ml: (
|
||||
value: Stitches.ScaleValue<"space"> | Stitches.PropertyValue<"marginLeft">
|
||||
) => ({
|
||||
marginLeft: value,
|
||||
}),
|
||||
mx: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'marginLeft' | 'marginRight'>) => ({
|
||||
mx: (
|
||||
value:
|
||||
| Stitches.ScaleValue<"space">
|
||||
| Stitches.PropertyValue<"marginLeft" | "marginRight">
|
||||
) => ({
|
||||
marginLeft: value,
|
||||
marginRight: value,
|
||||
}),
|
||||
my: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'marginTop' | 'marginBottom'>) => ({
|
||||
my: (
|
||||
value:
|
||||
| Stitches.ScaleValue<"space">
|
||||
| Stitches.PropertyValue<"marginTop" | "marginBottom">
|
||||
) => ({
|
||||
marginTop: value,
|
||||
marginBottom: value,
|
||||
}),
|
||||
// Abbreviated margin properties
|
||||
p: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'padding'>) => ({
|
||||
p: (
|
||||
value: Stitches.ScaleValue<"space"> | Stitches.PropertyValue<"padding">
|
||||
) => ({
|
||||
padding: value,
|
||||
}),
|
||||
pt: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'paddingTop'>) => ({
|
||||
pt: (
|
||||
value: Stitches.ScaleValue<"space"> | Stitches.PropertyValue<"paddingTop">
|
||||
) => ({
|
||||
paddingTop: value,
|
||||
}),
|
||||
pr: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'paddingRight'>) => ({
|
||||
pr: (
|
||||
value:
|
||||
| Stitches.ScaleValue<"space">
|
||||
| Stitches.PropertyValue<"paddingRight">
|
||||
) => ({
|
||||
paddingRight: value,
|
||||
}),
|
||||
pb: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'paddingBottom'>) => ({
|
||||
pb: (
|
||||
value:
|
||||
| Stitches.ScaleValue<"space">
|
||||
| Stitches.PropertyValue<"paddingBottom">
|
||||
) => ({
|
||||
paddingBottom: value,
|
||||
}),
|
||||
pl: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'paddingLeft'>) => ({
|
||||
pl: (
|
||||
value:
|
||||
| Stitches.ScaleValue<"space">
|
||||
| Stitches.PropertyValue<"paddingLeft">
|
||||
) => ({
|
||||
paddingLeft: value,
|
||||
}),
|
||||
px: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'paddingLeft' | 'paddingRight'>) => ({
|
||||
px: (
|
||||
value:
|
||||
| Stitches.ScaleValue<"space">
|
||||
| Stitches.PropertyValue<"paddingLeft" | "paddingRight">
|
||||
) => ({
|
||||
paddingLeft: value,
|
||||
paddingRight: value,
|
||||
}),
|
||||
py: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'paddingTop' | 'paddingBottom'>) => ({
|
||||
py: (
|
||||
value:
|
||||
| Stitches.ScaleValue<"space">
|
||||
| Stitches.PropertyValue<"paddingTop" | "paddingBottom">
|
||||
) => ({
|
||||
paddingTop: value,
|
||||
paddingBottom: value,
|
||||
}),
|
||||
|
||||
// A property for applying width/height together
|
||||
size: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'width' | 'height'>) => ({
|
||||
size: (
|
||||
value:
|
||||
| Stitches.ScaleValue<"space">
|
||||
| Stitches.PropertyValue<"width" | "height">
|
||||
) => ({
|
||||
width: value,
|
||||
height: value,
|
||||
}),
|
||||
@@ -280,46 +334,45 @@ export const {
|
||||
// }),
|
||||
|
||||
// A property to apply linear gradient
|
||||
linearGradient: (value: Stitches.ScaleValue<'space'>) => ({
|
||||
linearGradient: (value: Stitches.ScaleValue<"space">) => ({
|
||||
backgroundImage: `linear-gradient(${value})`,
|
||||
}),
|
||||
|
||||
// An abbreviated property for border-radius
|
||||
br: (value: Stitches.ScaleValue<'space'>) => ({
|
||||
br: (value: Stitches.ScaleValue<"space">) => ({
|
||||
borderRadius: value,
|
||||
}),
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export const darkTheme = createTheme('dark', {
|
||||
export const darkTheme = createTheme("dark", {
|
||||
colors: {
|
||||
...grayDark,
|
||||
...blueDark,
|
||||
...redDark,
|
||||
...greenDark,
|
||||
...plumDark,
|
||||
...crimsonDark,
|
||||
...grassDark,
|
||||
...slateDark,
|
||||
...mauveDark,
|
||||
...pinkDark,
|
||||
...yellowDark,
|
||||
...amberDark,
|
||||
...purpleDark,
|
||||
deep: 'rgb(10, 10, 10)'
|
||||
...redDark,
|
||||
deep: "rgb(10, 10, 10)",
|
||||
// backgroundA: transparentize(0.1, grayDark.gray1),
|
||||
},
|
||||
});
|
||||
|
||||
export const globalStyles = globalCss({
|
||||
// body: { backgroundColor: '$background', color: '$text', fontFamily: 'Helvetica' },
|
||||
'html, body': {
|
||||
backgroundColor: '$gray1',
|
||||
color: '$gray12',
|
||||
fontFamily: '$body',
|
||||
fontSize: '$md',
|
||||
'-webkit-font-smoothing': 'antialiased',
|
||||
'-moz-osx-font-smoothing': 'grayscale'
|
||||
"html, body": {
|
||||
backgroundColor: "$mauve2",
|
||||
color: "$mauve12",
|
||||
fontFamily: "$body",
|
||||
fontSize: "$md",
|
||||
"-webkit-font-smoothing": "antialiased",
|
||||
"-moz-osx-font-smoothing": "grayscale",
|
||||
},
|
||||
a: {
|
||||
color: "inherit",
|
||||
textDecoration: "none",
|
||||
},
|
||||
'a': {
|
||||
color: 'inherit',
|
||||
textDecoration: 'none'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -6,8 +6,51 @@ body,
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* overflow-y: hidden; */
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.monaco-hover p {
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
|
||||
.gutter {
|
||||
position: relative;
|
||||
transition: border-color 0.3s, background-color 0.3s;
|
||||
}
|
||||
|
||||
.gutter-vertical {
|
||||
margin-top: -4px;
|
||||
}
|
||||
.gutter-horizontal {
|
||||
margin-left: -4px;
|
||||
}
|
||||
.gutter-vertical:hover {
|
||||
cursor: row-resize;
|
||||
background-color: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
html.light .gutter-vertical:hover {
|
||||
background-color: rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.gutter-horizontal:hover {
|
||||
cursor: col-resize;
|
||||
background-color: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
html.light .gutter-horizontal:hover {
|
||||
background-color: rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
/* Adjust Monaco tooltip stylings */
|
||||
.markdown-hover h3 {
|
||||
margin: 0;
|
||||
}
|
||||
.monaco-editor .monaco-hover hr {
|
||||
margin: 8px 0;
|
||||
}
|
||||
.monaco-editor .monaco-hover {
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"inherit": true,
|
||||
"rules": [
|
||||
{
|
||||
"background": "1a1d1e",
|
||||
"background": "161618",
|
||||
"token": ""
|
||||
},
|
||||
{
|
||||
@@ -182,10 +182,10 @@
|
||||
],
|
||||
"colors": {
|
||||
"editor.foreground": "#D0D0FF",
|
||||
"editor.background": "#232326",
|
||||
"editor.background": "#1C1C1F",
|
||||
"editor.selectionBackground": "#ffffff30",
|
||||
"editor.lineHighlightBackground": "#ffffff20",
|
||||
"editorCursor.foreground": "#7070FF",
|
||||
"editorWhitespace.foreground": "#BFBFBF"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"inherit": true,
|
||||
"rules": [
|
||||
{
|
||||
"background": "FFFFFF",
|
||||
"background": "F4F2F4",
|
||||
"token": ""
|
||||
},
|
||||
{
|
||||
@@ -89,10 +89,10 @@
|
||||
],
|
||||
"colors": {
|
||||
"editor.foreground": "#000000",
|
||||
"editor.background": "#f4f2f4",
|
||||
"editor.background": "#F9F8F9",
|
||||
"editor.selectionBackground": "#B5D5FF",
|
||||
"editor.lineHighlightBackground": "#00000012",
|
||||
"editorCursor.foreground": "#000000",
|
||||
"editorWhitespace.foreground": "#BFBFBF"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
"node_modules",
|
||||
"*.md"
|
||||
]
|
||||
}
|
||||
|
||||
41
utils/hookOnCalculator.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
export const tts = {
|
||||
ttPAYMENT: 0,
|
||||
ttESCROW_CREATE: 1,
|
||||
ttESCROW_FINISH: 2,
|
||||
ttACCOUNT_SET: 3,
|
||||
ttESCROW_CANCEL: 4,
|
||||
ttREGULAR_KEY_SET: 5,
|
||||
ttOFFER_CREATE: 7,
|
||||
ttOFFER_CANCEL: 8,
|
||||
ttTICKET_CREATE: 10,
|
||||
ttSIGNER_LIST_SET: 12,
|
||||
ttPAYCHAN_CREATE: 13,
|
||||
ttPAYCHAN_FUND: 14,
|
||||
ttPAYCHAN_CLAIM: 15,
|
||||
ttCHECK_CREATE: 16,
|
||||
ttCHECK_CASH: 17,
|
||||
ttCHECK_CANCEL: 18,
|
||||
ttDEPOSIT_PREAUTH: 19,
|
||||
ttTRUST_SET: 20,
|
||||
ttACCOUNT_DELETE: 21,
|
||||
ttHOOK_SET: 22
|
||||
};
|
||||
|
||||
export type TTS = typeof tts;
|
||||
|
||||
const calculateHookOn = (arr: (keyof TTS)[]) => {
|
||||
let start = '0x00000000003ff5bf';
|
||||
arr.forEach(n => {
|
||||
let v = BigInt(start);
|
||||
v ^= (BigInt(1) << BigInt(tts[n as keyof TTS]));
|
||||
let s = v.toString(16);
|
||||
let l = s.length;
|
||||
if (l < 16)
|
||||
s = '0'.repeat(16 - l) + s;
|
||||
s = '0x' + s;
|
||||
start = s;
|
||||
})
|
||||
return start.substring(2);
|
||||
}
|
||||
|
||||
export default calculateHookOn
|
||||
21
utils/json.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export const extractJSON = (str?: string) => {
|
||||
if (!str) return
|
||||
let firstOpen = 0, firstClose = 0, candidate = '';
|
||||
firstOpen = str.indexOf('{', firstOpen + 1);
|
||||
do {
|
||||
firstClose = str.lastIndexOf('}');
|
||||
if (firstClose <= firstOpen) {
|
||||
return;
|
||||
}
|
||||
do {
|
||||
candidate = str.substring(firstOpen, firstClose + 1);
|
||||
try {
|
||||
let result = JSON.parse(candidate);
|
||||
return { result, start: firstOpen < 0 ? 0 : firstOpen, end: firstClose }
|
||||
}
|
||||
catch (e) { }
|
||||
firstClose = str.substring(0, firstClose).lastIndexOf('}');
|
||||
} while (firstClose > firstOpen);
|
||||
firstOpen = str.indexOf('{', firstOpen + 1);
|
||||
} while (firstOpen != -1);
|
||||
}
|
||||
714
utils/wat-highlight.ts
Normal file
@@ -0,0 +1,714 @@
|
||||
// 'WebAssembly Text Format' Monarch language
|
||||
|
||||
import type monaco from 'monaco-editor';
|
||||
|
||||
const WebAssemblyTextLanguage: { config: monaco.languages.LanguageConfiguration, tokens: monaco.languages.IMonarchLanguage } = {
|
||||
config: {
|
||||
brackets: [
|
||||
["(", ")"],
|
||||
["if", "end"],
|
||||
["loop", "end"],
|
||||
["block", "end"],
|
||||
],
|
||||
autoClosingPairs: [
|
||||
{ open: "(", close: ")" },
|
||||
{ open: "if", close: "end" },
|
||||
{ open: "loop", close: "end" },
|
||||
{ open: "block", close: "end" },
|
||||
],
|
||||
surroundingPairs: [
|
||||
{ open: "(", close: ")" },
|
||||
{ open: "if", close: "end" },
|
||||
{ open: "loop", close: "end" },
|
||||
{ open: "block", close: "end" },
|
||||
],
|
||||
},
|
||||
tokens: {
|
||||
keywords: [
|
||||
"module",
|
||||
"import",
|
||||
"export",
|
||||
"memory",
|
||||
"data",
|
||||
"table",
|
||||
"elem",
|
||||
"start",
|
||||
"func",
|
||||
"tag",
|
||||
"type",
|
||||
"param",
|
||||
"result",
|
||||
"global",
|
||||
"local",
|
||||
"mut",
|
||||
"struct",
|
||||
"array",
|
||||
"field",
|
||||
],
|
||||
types: [
|
||||
"i8",
|
||||
"i16",
|
||||
"i32",
|
||||
"i64",
|
||||
"f32",
|
||||
"f64",
|
||||
"v128",
|
||||
"i31ref",
|
||||
"eqref",
|
||||
"anyref",
|
||||
"dataref",
|
||||
"externref",
|
||||
"funcref",
|
||||
"exnref",
|
||||
"extern",
|
||||
"null",
|
||||
"any",
|
||||
"eq",
|
||||
],
|
||||
instructions: [
|
||||
"pop",
|
||||
"nop",
|
||||
"drop",
|
||||
"data.drop",
|
||||
"elem.drop",
|
||||
"local.get",
|
||||
"local.set",
|
||||
"local.tee",
|
||||
"global.get",
|
||||
"global.set",
|
||||
"tuple.make",
|
||||
"tuple.extract",
|
||||
"select",
|
||||
"v128.const",
|
||||
"v128.and",
|
||||
"v128.or",
|
||||
"v128.xor",
|
||||
"v128.not",
|
||||
"v128.andnot",
|
||||
"v128.bitselect",
|
||||
"v128.load",
|
||||
"v128.load8x8_s",
|
||||
"v128.load8x8_u",
|
||||
"v128.load16x4_s",
|
||||
"v128.load16x4_u",
|
||||
"v128.load32x2_s",
|
||||
"v128.load32x2_u",
|
||||
"v128.load8_lane",
|
||||
"v128.load16_lane",
|
||||
"v128.load32_lane",
|
||||
"v128.load64_lane",
|
||||
"v128.load8_splat",
|
||||
"v128.load16_splat",
|
||||
"v128.load32_splat",
|
||||
"v128.load64_splat",
|
||||
"v128.load32_zero",
|
||||
"v128.load64_zero",
|
||||
"v128.store",
|
||||
"v128.store8_lane",
|
||||
"v128.store16_lane",
|
||||
"v128.store32_lane",
|
||||
"v128.store64_lane",
|
||||
"v128.any_true",
|
||||
"i8x16.shuffle",
|
||||
"i8x16.swizzle",
|
||||
"i8x16.bitmask",
|
||||
"i8x16.splat",
|
||||
"i8x16.popcnt",
|
||||
"i8x16.replace_lane",
|
||||
"i8x16.extract_lane_s",
|
||||
"i8x16.extract_lane_u",
|
||||
"i8x16.all_true",
|
||||
"i8x16.abs",
|
||||
"i8x16.add",
|
||||
"i8x16.add_sat_s",
|
||||
"i8x16.add_sat_u",
|
||||
"i8x16.sub",
|
||||
"i8x16.sub_sat_s",
|
||||
"i8x16.sub_sat_u",
|
||||
"i8x16.mul",
|
||||
"i8x16.neg",
|
||||
"i8x16.shl",
|
||||
"i8x16.shr_s",
|
||||
"i8x16.shr_u",
|
||||
"i8x16.eq",
|
||||
"i8x16.ne",
|
||||
"i8x16.lt_s",
|
||||
"i8x16.lt_u",
|
||||
"i8x16.le_s",
|
||||
"i8x16.le_u",
|
||||
"i8x16.gt_s",
|
||||
"i8x16.gt_u",
|
||||
"i8x16.ge_s",
|
||||
"i8x16.ge_u",
|
||||
"i8x16.min_s",
|
||||
"i8x16.min_u",
|
||||
"i8x16.max_s",
|
||||
"i8x16.max_u",
|
||||
"i8x16.avgr_u",
|
||||
"i8x16.narrow_i16x8_s",
|
||||
"i8x16.narrow_i16x8_u",
|
||||
"i16x8.bitmask",
|
||||
"i16x8.splat",
|
||||
"i16x8.load_8x8_s",
|
||||
"i16x8.load_8x8_u",
|
||||
"i16x8.replace_lane",
|
||||
"i16x8.extract_lane_s",
|
||||
"i16x8.extract_lane_u",
|
||||
"i16x8.extend_low_i8x16_s",
|
||||
"i16x8.extend_high_i8x16_s",
|
||||
"i16x8.extend_low_i8x16_u",
|
||||
"i16x8.extend_high_i8x16_u",
|
||||
"i16x8.all_true",
|
||||
"i16x8.abs",
|
||||
"i16x8.add",
|
||||
"i16x8.add_sat_s",
|
||||
"i16x8.add_sat_u",
|
||||
"i16x8.extadd_pairwise_i8x16_s",
|
||||
"i16x8.extadd_pairwise_i8x16_u",
|
||||
"i16x8.sub",
|
||||
"i16x8.sub_sat_s",
|
||||
"i16x8.sub_sat_u",
|
||||
"i16x8.q15mulr_sat_s",
|
||||
"i16x8.mul",
|
||||
"i16x8.extmul_low_i8x16_s",
|
||||
"i16x8.extmul_high_i8x16_s",
|
||||
"i16x8.extmul_low_i8x16_u",
|
||||
"i16x8.extmul_high_i8x16_u",
|
||||
"i16x8.neg",
|
||||
"i16x8.shl",
|
||||
"i16x8.shr_s",
|
||||
"i16x8.shr_u",
|
||||
"i16x8.eq",
|
||||
"i16x8.ne",
|
||||
"i16x8.lt_s",
|
||||
"i16x8.lt_u",
|
||||
"i16x8.le_s",
|
||||
"i16x8.le_u",
|
||||
"i16x8.gt_s",
|
||||
"i16x8.gt_u",
|
||||
"i16x8.ge_s",
|
||||
"i16x8.ge_u",
|
||||
"i16x8.min_s",
|
||||
"i16x8.min_u",
|
||||
"i16x8.max_s",
|
||||
"i16x8.max_u",
|
||||
"i16x8.avgr_u",
|
||||
"i16x8.narrow_i32x4_s",
|
||||
"i16x8.narrow_i32x4_u",
|
||||
"i32x4.bitmask",
|
||||
"i32x4.splat",
|
||||
"i32x4.load_16x4_s",
|
||||
"i32x4.load_16x4_u",
|
||||
"i32x4.replace_lane",
|
||||
"i32x4.extract_lane",
|
||||
"i32x4.extend_low_i16x8_s",
|
||||
"i32x4.extend_high_i16x8_s",
|
||||
"i32x4.extend_low_i16x8_u",
|
||||
"i32x4.extend_high_i16x8_u",
|
||||
"i32x4.all_true",
|
||||
"i32x4.abs",
|
||||
"i32x4.add",
|
||||
"i32x4.extadd_pairwise_i16x8_s",
|
||||
"i32x4.extadd_pairwise_i16x8_u",
|
||||
"i32x4.sub",
|
||||
"i32x4.mul",
|
||||
"i32x4.extmul_low_i16x8_s",
|
||||
"i32x4.extmul_high_i16x8_s",
|
||||
"i32x4.extmul_low_i16x8_u",
|
||||
"i32x4.extmul_high_i16x8_u",
|
||||
"i32x4.neg",
|
||||
"i32x4.shl",
|
||||
"i32x4.shr_s",
|
||||
"i32x4.shr_u",
|
||||
"i32x4.eq",
|
||||
"i32x4.ne",
|
||||
"i32x4.lt_s",
|
||||
"i32x4.lt_u",
|
||||
"i32x4.le_s",
|
||||
"i32x4.le_u",
|
||||
"i32x4.gt_s",
|
||||
"i32x4.gt_u",
|
||||
"i32x4.ge_s",
|
||||
"i32x4.ge_u",
|
||||
"i32x4.min_s",
|
||||
"i32x4.min_u",
|
||||
"i32x4.max_s",
|
||||
"i32x4.max_u",
|
||||
"i32x4.trunc_sat_f32x4_s",
|
||||
"i32x4.trunc_sat_f32x4_u",
|
||||
"i32x4.trunc_sat_f64x2_s_zero",
|
||||
"i32x4.trunc_sat_f64x2_u_zero",
|
||||
"i32x4.dot_i16x8_s",
|
||||
"i64x2.bitmask",
|
||||
"i64x2.splat",
|
||||
"i64x2.load32x2_s",
|
||||
"i64x2.load32x2_u",
|
||||
"i64x2.replace_lane",
|
||||
"i64x2.extract_lane",
|
||||
"i64x2.extend_low_i32x4_s",
|
||||
"i64x2.extend_high_i32x4_s",
|
||||
"i64x2.extend_low_i32x4_u",
|
||||
"i64x2.extend_high_i32x4_u",
|
||||
"i64x2.all_true",
|
||||
"i64x2.abs",
|
||||
"i64x2.add",
|
||||
"i64x2.sub",
|
||||
"i64x2.mul",
|
||||
"i64x2.neg",
|
||||
"i64x2.shl",
|
||||
"i64x2.shr_s",
|
||||
"i64x2.shr_u",
|
||||
"f32x4.splat",
|
||||
"f32x4.replace_lane",
|
||||
"f32x4.extract_lane",
|
||||
"f32x4.add",
|
||||
"f32x4.sub",
|
||||
"f32x4.mul",
|
||||
"i64x2.extmul_low_i32x4_s",
|
||||
"i64x2.extmul_high_i32x4_s",
|
||||
"i64x2.extmul_low_i32x4_u",
|
||||
"i64x2.extmul_high_i32x4_u",
|
||||
"i64x2.eq",
|
||||
"i64x2.ne",
|
||||
"i64x2.lt_s",
|
||||
"i64x2.le_s",
|
||||
"i64x2.gt_s",
|
||||
"i64x2.ge_s",
|
||||
"f32x4.neg",
|
||||
"f32x4.eq",
|
||||
"f32x4.ne",
|
||||
"f32x4.lt",
|
||||
"f32x4.le",
|
||||
"f32x4.gt",
|
||||
"f32x4.ge",
|
||||
"f32x4.abs",
|
||||
"f32x4.min",
|
||||
"f32x4.pmin",
|
||||
"f32x4.max",
|
||||
"f32x4.pmax",
|
||||
"f32x4.div",
|
||||
"f32x4.sqrt",
|
||||
"f32x4.ceil",
|
||||
"f32x4.floor",
|
||||
"f32x4.trunc",
|
||||
"f32x4.nearest",
|
||||
"f32x4.demote_f64x2_zero",
|
||||
"f32x4.convert_i32x4_s",
|
||||
"f32x4.convert_i32x4_u",
|
||||
"f64x2.splat",
|
||||
"f64x2.replace_lane",
|
||||
"f64x2.extract_lane",
|
||||
"f64x2.add",
|
||||
"f64x2.sub",
|
||||
"f64x2.mul",
|
||||
"f64x2.neg",
|
||||
"f64x2.eq",
|
||||
"f64x2.ne",
|
||||
"f64x2.lt",
|
||||
"f64x2.le",
|
||||
"f64x2.gt",
|
||||
"f64x2.ge",
|
||||
"f64x2.abs",
|
||||
"f64x2.min",
|
||||
"f64x2.max",
|
||||
"f64x2.pmin",
|
||||
"f64x2.pmax",
|
||||
"f64x2.div",
|
||||
"f64x2.sqrt",
|
||||
"f64x2.ceil",
|
||||
"f64x2.floor",
|
||||
"f64x2.trunc",
|
||||
"f64x2.nearest",
|
||||
"f64x2.promote_low_f32x4",
|
||||
"f64x2.convert_low_i32x4_s",
|
||||
"f64x2.convert_low_i32x4_u",
|
||||
"i32.atomic.load",
|
||||
"i32.atomic.load8_u",
|
||||
"i32.atomic.load16_u",
|
||||
"i32.atomic.store",
|
||||
"i32.atomic.store8",
|
||||
"i32.atomic.store16",
|
||||
"i32.atomic.rmw.add",
|
||||
"i32.atomic.rmw.sub",
|
||||
"i32.atomic.rmw.and",
|
||||
"i32.atomic.rmw.or",
|
||||
"i32.atomic.rmw.xor",
|
||||
"i32.atomic.rmw.xchg",
|
||||
"i32.atomic.rmw.cmpxchg",
|
||||
"i32.atomic.rmw8.add_u",
|
||||
"i32.atomic.rmw8.sub_u",
|
||||
"i32.atomic.rmw8.and_u",
|
||||
"i32.atomic.rmw8.or_u",
|
||||
"i32.atomic.rmw8.xor_u",
|
||||
"i32.atomic.rmw8.xchg_u",
|
||||
"i32.atomic.rmw8.cmpxchg_u",
|
||||
"i32.atomic.rmw16.add_u",
|
||||
"i32.atomic.rmw16.sub_u",
|
||||
"i32.atomic.rmw16.and_u",
|
||||
"i32.atomic.rmw16.or_u",
|
||||
"i32.atomic.rmw16.xor_u",
|
||||
"i32.atomic.rmw16.xchg_u",
|
||||
"i32.atomic.rmw16.cmpxchg_u",
|
||||
"i64.atomic.load",
|
||||
"i64.atomic.load8_u",
|
||||
"i64.atomic.load16_u",
|
||||
"i64.atomic.load32_u",
|
||||
"i64.atomic.store",
|
||||
"i64.atomic.store8",
|
||||
"i64.atomic.store16",
|
||||
"i64.atomic.store32",
|
||||
"i64.atomic.rmw.add",
|
||||
"i64.atomic.rmw.sub",
|
||||
"i64.atomic.rmw.and",
|
||||
"i64.atomic.rmw.or",
|
||||
"i64.atomic.rmw.xor",
|
||||
"i64.atomic.rmw.xchg",
|
||||
"i64.atomic.rmw.cmpxchg",
|
||||
"i64.atomic.rmw8.add_u",
|
||||
"i64.atomic.rmw8.sub_u",
|
||||
"i64.atomic.rmw8.and_u",
|
||||
"i64.atomic.rmw8.or_u",
|
||||
"i64.atomic.rmw8.xor_u",
|
||||
"i64.atomic.rmw8.xchg_u",
|
||||
"i64.atomic.rmw8.cmpxchg_u",
|
||||
"i64.atomic.rmw16.add_u",
|
||||
"i64.atomic.rmw16.sub_u",
|
||||
"i64.atomic.rmw16.and_u",
|
||||
"i64.atomic.rmw16.or_u",
|
||||
"i64.atomic.rmw16.xor_u",
|
||||
"i64.atomic.rmw16.xchg_u",
|
||||
"i64.atomic.rmw16.cmpxchg_u",
|
||||
"i64.atomic.rmw32.add_u",
|
||||
"i64.atomic.rmw32.sub_u",
|
||||
"i64.atomic.rmw32.and_u",
|
||||
"i64.atomic.rmw32.or_u",
|
||||
"i64.atomic.rmw32.xor_u",
|
||||
"i64.atomic.rmw32.xchg_u",
|
||||
"i64.atomic.rmw32.cmpxchg_u",
|
||||
"atomic.fence",
|
||||
"func.bind",
|
||||
"ref",
|
||||
"ref.eq",
|
||||
"ref.null",
|
||||
"ref.is_null",
|
||||
"ref.is_func",
|
||||
"ref.is_data",
|
||||
"ref.is_i31",
|
||||
"ref.as_func",
|
||||
"ref.as_non_null",
|
||||
"ref.as_data",
|
||||
"ref.as_i31",
|
||||
"ref.func",
|
||||
"ref.cast",
|
||||
"ref.cast_static",
|
||||
"ref.test",
|
||||
"ref.test_static",
|
||||
"table.get",
|
||||
"table.set",
|
||||
"table.size",
|
||||
"table.grow",
|
||||
"table.fill",
|
||||
"table.init",
|
||||
"table.copy",
|
||||
"throw",
|
||||
"rethrow",
|
||||
"i32.load",
|
||||
"i32.load8_s",
|
||||
"i32.load8_u",
|
||||
"i32.load16_s",
|
||||
"i32.load16_u",
|
||||
"i32.store",
|
||||
"i32.store8",
|
||||
"i32.store16",
|
||||
"i32.const",
|
||||
"i32.eqz",
|
||||
"i32.eq",
|
||||
"i32.ne",
|
||||
"i32.lt_s",
|
||||
"i32.lt_u",
|
||||
"i32.le_s",
|
||||
"i32.le_u",
|
||||
"i32.gt_s",
|
||||
"i32.gt_u",
|
||||
"i32.ge_s",
|
||||
"i32.ge_u",
|
||||
"i32.clz",
|
||||
"i32.ctz",
|
||||
"i32.popcnt",
|
||||
"i32.add",
|
||||
"i32.sub",
|
||||
"i32.mul",
|
||||
"i32.div_s",
|
||||
"i32.div_u",
|
||||
"i32.rem_s",
|
||||
"i32.rem_u",
|
||||
"i32.and",
|
||||
"i32.or",
|
||||
"i32.xor",
|
||||
"i32.shl",
|
||||
"i32.shr_s",
|
||||
"i32.shr_u",
|
||||
"i32.rotl",
|
||||
"i32.rotr",
|
||||
"i32.wrap_i64",
|
||||
"i32.trunc_f32_s",
|
||||
"i32.trunc_f32_u",
|
||||
"i32.trunc_f64_s",
|
||||
"i32.trunc_f64_u",
|
||||
"i32.reinterpret_f32",
|
||||
"i64.load",
|
||||
"i64.load8_s",
|
||||
"i64.load8_u",
|
||||
"i64.load16_s",
|
||||
"i64.load16_u",
|
||||
"i64.load32_s",
|
||||
"i64.load32_u",
|
||||
"i64.store",
|
||||
"i64.store8",
|
||||
"i64.store16",
|
||||
"i64.store32",
|
||||
"i64.const",
|
||||
"i64.eqz",
|
||||
"i64.eq",
|
||||
"i64.ne",
|
||||
"i64.lt_s",
|
||||
"i64.lt_u",
|
||||
"i64.le_s",
|
||||
"i64.le_u",
|
||||
"i64.gt_s",
|
||||
"i64.gt_u",
|
||||
"i64.ge_s",
|
||||
"i64.ge_u",
|
||||
"i64.clz",
|
||||
"i64.ctz",
|
||||
"i64.popcnt",
|
||||
"i64.add",
|
||||
"i64.sub",
|
||||
"i64.mul",
|
||||
"i64.div_s",
|
||||
"i64.div_u",
|
||||
"i64.rem_s",
|
||||
"i64.rem_u",
|
||||
"i64.and",
|
||||
"i64.or",
|
||||
"i64.xor",
|
||||
"i64.shl",
|
||||
"i64.shr_s",
|
||||
"i64.shr_u",
|
||||
"i64.rotl",
|
||||
"i64.rotr",
|
||||
"i64.extend_i32_s",
|
||||
"i64.extend_i32_u",
|
||||
"i64.trunc_f32_s",
|
||||
"i64.trunc_f32_u",
|
||||
"i64.trunc_f64_s",
|
||||
"i64.trunc_f64_u",
|
||||
"i64.reinterpret_f64",
|
||||
"f32.load",
|
||||
"f32.store",
|
||||
"f32.const",
|
||||
"f32.eq",
|
||||
"f32.ne",
|
||||
"f32.lt",
|
||||
"f32.le",
|
||||
"f32.gt",
|
||||
"f32.ge",
|
||||
"f32.abs",
|
||||
"f32.neg",
|
||||
"f32.ceil",
|
||||
"f32.floor",
|
||||
"f32.trunc",
|
||||
"f32.nearest",
|
||||
"f32.sqrt",
|
||||
"f32.add",
|
||||
"f32.sub",
|
||||
"f32.mul",
|
||||
"f32.div",
|
||||
"f32.min",
|
||||
"f32.max",
|
||||
"f32.copysign",
|
||||
"f32.convert_i32_s",
|
||||
"f32.convert_i32_u",
|
||||
"f32.convert_i64_s",
|
||||
"f32.convert_i64_u",
|
||||
"f32.demote_f64",
|
||||
"f32.reinterpret_i32",
|
||||
"f64.load",
|
||||
"f64.store",
|
||||
"f64.const",
|
||||
"f64.eq",
|
||||
"f64.ne",
|
||||
"f64.lt",
|
||||
"f64.le",
|
||||
"f64.gt",
|
||||
"f64.ge",
|
||||
"f64.abs",
|
||||
"f64.neg",
|
||||
"f64.ceil",
|
||||
"f64.floor",
|
||||
"f64.trunc",
|
||||
"f64.nearest",
|
||||
"f64.sqrt",
|
||||
"f64.add",
|
||||
"f64.sub",
|
||||
"f64.mul",
|
||||
"f64.div",
|
||||
"f64.min",
|
||||
"f64.max",
|
||||
"f64.copysign",
|
||||
"f64.convert_i32_s",
|
||||
"f64.convert_i32_u",
|
||||
"f64.convert_i64_s",
|
||||
"f64.convert_i64_u",
|
||||
"f64.promote_f32",
|
||||
"f64.reinterpret_i64",
|
||||
"i32.extend8_s",
|
||||
"i32.extend16_s",
|
||||
"i64.extend8_s",
|
||||
"i64.extend16_s",
|
||||
"i64.extend32_s",
|
||||
"i32.trunc_sat_f32_s",
|
||||
"i32.trunc_sat_f32_u",
|
||||
"i32.trunc_sat_f64_s",
|
||||
"i32.trunc_sat_f64_u",
|
||||
"i64.trunc_sat_f32_s",
|
||||
"i64.trunc_sat_f32_u",
|
||||
"i64.trunc_sat_f64_s",
|
||||
"i64.trunc_sat_f64_u",
|
||||
"memory.size",
|
||||
"memory.grow",
|
||||
"memory.copy",
|
||||
"memory.fill",
|
||||
"memory.init",
|
||||
"memory.atomic.notify",
|
||||
"memory.atomic.wait32",
|
||||
"memory.atomic.wait64",
|
||||
"i31.new",
|
||||
"i31.get_u",
|
||||
"i31.get_s",
|
||||
"array.new",
|
||||
"array.new_default",
|
||||
"array.init",
|
||||
"array.init_static",
|
||||
"array.get",
|
||||
"array.get_s",
|
||||
"array.get_u",
|
||||
"array.set",
|
||||
"array.len",
|
||||
"array.copy",
|
||||
"struct.new",
|
||||
"struct.new_default",
|
||||
"struct.new_with_rtt",
|
||||
"struct.new_default_with_rtt",
|
||||
"struct.get",
|
||||
"struct.get_s",
|
||||
"struct.get_u",
|
||||
"struct.set",
|
||||
"rtt.canon",
|
||||
"rtt.sub",
|
||||
"rtt.fresh_sub",
|
||||
],
|
||||
controlInstructions: [
|
||||
"block",
|
||||
"loop",
|
||||
"if",
|
||||
"else",
|
||||
"then",
|
||||
"end",
|
||||
"do",
|
||||
"let",
|
||||
"br",
|
||||
"br_if",
|
||||
"br_table",
|
||||
"br_on_exn",
|
||||
"br_on_null",
|
||||
"br_on_non_null",
|
||||
"br_on_cast",
|
||||
"br_on_cast_static",
|
||||
"br_on_cast_fail",
|
||||
"br_on_cast_static_fail",
|
||||
"br_on_func",
|
||||
"br_on_non_func",
|
||||
"br_on_data",
|
||||
"br_on_non_data",
|
||||
"br_on_i31",
|
||||
"br_on_non_i31",
|
||||
"call",
|
||||
"call_indirect",
|
||||
"call_ref",
|
||||
"return",
|
||||
"return_call",
|
||||
"return_call_indirect",
|
||||
"return_call_ref",
|
||||
"try",
|
||||
"catch",
|
||||
"catch_all",
|
||||
"delegate",
|
||||
"unreachable",
|
||||
],
|
||||
|
||||
escapes:
|
||||
/\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
|
||||
digits: /\d+(_+\d+)*/,
|
||||
octaldigits: /[0-7]+(_+[0-7]+)*/,
|
||||
binarydigits: /[0-1]+(_+[0-1]+)*/,
|
||||
hexdigits: /[[0-9a-fA-F]+(_+[0-9a-fA-F]+)*/,
|
||||
|
||||
tokenizer: {
|
||||
root: [
|
||||
// whitespace
|
||||
{ include: "@whitespace" },
|
||||
|
||||
// strings
|
||||
[/"([^"\\]|\\.)*$/, "string.invalid"], // non-teminated string
|
||||
[/"/, "string", "@string"],
|
||||
|
||||
// numbers (not all of these are generated, but here to be sure)
|
||||
[/(@digits)[eE]([\-+]?(@digits))?[fFdD]?/, "number.float"],
|
||||
[/(@digits)\.(@digits)([eE][\-+]?(@digits))?[fFdD]?/, "number.float"],
|
||||
[/0[xX](@hexdigits)[Ll]?/, "number.hex"],
|
||||
[/0(@octaldigits)[Ll]?/, "number.octal"],
|
||||
[/0[bB](@binarydigits)[Ll]?/, "number.binary"],
|
||||
[/(@digits)[fFdD]/, "number.float"],
|
||||
[/(@digits)[lL]?/, "number"],
|
||||
|
||||
// variable names
|
||||
[/\$[^\s\)]*/, { token: "identifier" }],
|
||||
|
||||
// instructions and types
|
||||
[
|
||||
/[a-z0-9_]+(?:\.[a-z0-9_]+)*/,
|
||||
{
|
||||
cases: {
|
||||
"@types": { token: "type.$0" },
|
||||
"@keywords": { token: "keyword.$0" },
|
||||
"@controlInstructions": { token: "controlInstruction.$0" },
|
||||
"@instructions": { token: "instruction.$0" },
|
||||
"@default": "identifier",
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
|
||||
string: [
|
||||
[/[^\\"]+/, "string"],
|
||||
[/@escapes/, "string.escape"],
|
||||
[/\\./, "string.escape.invalid"],
|
||||
[/"/, "string", "@pop"],
|
||||
],
|
||||
|
||||
whitespace: [
|
||||
[/[ \t\r\n]+/, ""],
|
||||
[/(;; )(ERROR |FAILURE )([^\n]*)/, ["comment", "error", ""]],
|
||||
[/(;; )(WARNING )([^\n]*)/, ["comment", "warning", ""]],
|
||||
[/(;; )(INFO )([^\n]*)/, ["comment", "info", ""]],
|
||||
[/(;; )(PEDANTIC )([^\n]*)/, ["comment", "pedantic", ""]],
|
||||
[/(;; +)(~+|\^)$/, ["comment", "underline"]],
|
||||
[/(;; )([^\n]*)/, ["comment", ""]],
|
||||
[/;;[^\n]*/, "comment"],
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default WebAssemblyTextLanguage;
|
||||
91
xrpl-hooks-docs/docs.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import hooksAccountBufLen from "./md/hooks-account-buf-len.md";
|
||||
import hooksAccountConvBufLen from "./md/hooks-account-conv-buf-len.md";
|
||||
import hooksAccountConvPure from "./md/hooks-account-conv-pure.md";
|
||||
import hooksArrayBufLen from "./md/hooks-array-buf-len.md";
|
||||
import hooksBurdenPrereq from "./md/hooks-burden-prereq.md";
|
||||
import hooksDetailBufLen from "./md/hooks-detail-buf-len.md";
|
||||
import hooksDetailPrereq from "./md/hooks-detail-prereq.md";
|
||||
import hooksEmitBufLen from "./md/hooks-emit-buf-len.md";
|
||||
import hooksEmitPrereq from "./md/hooks-emit-prereq.md";
|
||||
import hooksEntryPointRecursion from "./md/hooks-entry-point-recursion.md";
|
||||
import hooksEntryPointsNeg from "./md/hooks-entry-points-neg.md";
|
||||
import hooksEntryPoints from "./md/hooks-entry-points.md";
|
||||
import hooksFeePrereq from "./md/hooks-fee-prereq.md";
|
||||
import hooksFieldAddBufLen from "./md/hooks-field-add-buf-len.md";
|
||||
import hooksFieldBufLen from "./md/hooks-field-buf-len.md";
|
||||
import hooksFieldDelBufLen from "./md/hooks-field-del-buf-len.md";
|
||||
import hooksFloatArithPure from "./md/hooks-float-arith-pure.md";
|
||||
import hooksFloatComparePure from "./md/hooks-float-compare-pure.md";
|
||||
import hooksFloatIntPure from "./md/hooks-float-int-pure.md";
|
||||
import hooksFloatManipPure from "./md/hooks-float-manip-pure.md";
|
||||
import hooksFloatOnePure from "./md/hooks-float-one-pure.md";
|
||||
import hooksFloatPure from "./md/hooks-float-pure.md";
|
||||
import hooksGuardCalled from "./md/hooks-guard-called.md";
|
||||
import hooksGuardInFor from "./md/hooks-guard-in-for.md";
|
||||
import hooksGuardInWhile from "./md/hooks-guard-in-while.md";
|
||||
import hooksHashBufLen from "./md/hooks-hash-buf-len.md";
|
||||
import hooksKeyletBufLen from "./md/hooks-keylet-buf-len.md";
|
||||
import hooksParamBufLen from "./md/hooks-param-buf-len.md";
|
||||
import hooksParamSetBufLen from "./md/hooks-param-set-buf-len.md";
|
||||
import hooksRaddrConvBufLen from "./md/hooks-raddr-conv-buf-len.md";
|
||||
import hooksRaddrConvPure from "./md/hooks-raddr-conv-pure.md";
|
||||
import hooksReserveLimit from "./md/hooks-reserve-limit.md";
|
||||
import hooksSlotHashBufLen from "./md/hooks-slot-hash-buf-len.md";
|
||||
import hooksSlotKeyletBufLen from "./md/hooks-slot-keylet-buf-len.md";
|
||||
import hooksSlotLimit from "./md/hooks-slot-limit.md";
|
||||
import hooksSlotSubLimit from "./md/hooks-slot-sub-limit.md";
|
||||
import hooksSlotTypeLimit from "./md/hooks-slot-type-limit.md";
|
||||
import hooksStateBufLen from "./md/hooks-state-buf-len.md";
|
||||
import hooksTransactionHashBufLen from "./md/hooks-transaction-hash-buf-len.md";
|
||||
import hooksTransactionSlotLimit from "./md/hooks-transaction-slot-limit.md";
|
||||
import hooksValidateBufLen from "./md/hooks-validate-buf-len.md";
|
||||
import hooksVerifyBufLen from "./md/hooks-verify-buf-len.md";
|
||||
|
||||
|
||||
|
||||
const docs: { [key: string]: string; } = {
|
||||
"hooks-account-buf-len": hooksAccountBufLen,
|
||||
"hooks-account-conv-buf-len": hooksAccountConvBufLen,
|
||||
"hooks-account-conv-pure": hooksAccountConvPure,
|
||||
"hooks-array-buf-len": hooksArrayBufLen,
|
||||
"hooks-burden-prereq": hooksBurdenPrereq,
|
||||
"hooks-detail-buf-len": hooksDetailBufLen,
|
||||
"hooks-detail-prereq": hooksDetailPrereq,
|
||||
"hooks-emit-buf-len": hooksEmitBufLen,
|
||||
"hooks-emit-prereq": hooksEmitPrereq,
|
||||
"hooks-entry-point-recursion": hooksEntryPointRecursion,
|
||||
"hooks-entry-points-neg": hooksEntryPointsNeg,
|
||||
"hooks-entry-points": hooksEntryPoints,
|
||||
"hooks-fee-prereq": hooksFeePrereq,
|
||||
"hooks-field-add-buf-len": hooksFieldAddBufLen,
|
||||
"hooks-field-buf-len": hooksFieldBufLen,
|
||||
"hooks-field-del-buf-len": hooksFieldDelBufLen,
|
||||
"hooks-float-arith-pure": hooksFloatArithPure,
|
||||
"hooks-float-compare-pure": hooksFloatComparePure,
|
||||
"hooks-float-int-pure": hooksFloatIntPure,
|
||||
"hooks-float-manip-pure": hooksFloatManipPure,
|
||||
"hooks-float-one-pure": hooksFloatOnePure,
|
||||
"hooks-float-pure": hooksFloatPure,
|
||||
"hooks-guard-called": hooksGuardCalled,
|
||||
"hooks-guard-in-for": hooksGuardInFor,
|
||||
"hooks-guard-in-while": hooksGuardInWhile,
|
||||
"hooks-hash-buf-len": hooksHashBufLen,
|
||||
"hooks-keylet-buf-len": hooksKeyletBufLen,
|
||||
"hooks-param-buf-len": hooksParamBufLen,
|
||||
"hooks-param-set-buf-len": hooksParamSetBufLen,
|
||||
"hooks-raddr-conv-buf-len": hooksRaddrConvBufLen,
|
||||
"hooks-raddr-conv-pure": hooksRaddrConvPure,
|
||||
"hooks-reserve-limit": hooksReserveLimit,
|
||||
"hooks-slot-hash-buf-len": hooksSlotHashBufLen,
|
||||
"hooks-slot-keylet-buf-len": hooksSlotKeyletBufLen,
|
||||
"hooks-slot-limit": hooksSlotLimit,
|
||||
"hooks-slot-sub-limit": hooksSlotSubLimit,
|
||||
"hooks-slot-type-limit": hooksSlotTypeLimit,
|
||||
"hooks-state-buf-len": hooksStateBufLen,
|
||||
"hooks-transaction-hash-buf-len": hooksTransactionHashBufLen,
|
||||
"hooks-transaction-slot-limit": hooksTransactionSlotLimit,
|
||||
"hooks-validate-buf-len": hooksValidateBufLen,
|
||||
"hooks-verify-buf-len": hooksVerifyBufLen,
|
||||
};
|
||||
|
||||
export default docs;
|
||||
5
xrpl-hooks-docs/md/hooks-account-buf-len.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# hooks-account-buf-len
|
||||
|
||||
Function [hook_account](https://xrpl-hooks.readme.io/v2.0/reference/hook_account) has fixed-size account ID output.
|
||||
|
||||
This check warns about too-small size of its output buffer (if it's specified by a constant - variable parameter is ignored).
|
||||
5
xrpl-hooks-docs/md/hooks-account-conv-buf-len.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# hooks-account-conv-buf-len
|
||||
|
||||
Function [util_raddr](https://xrpl-hooks.readme.io/v2.0/reference/util_raddr) has fixed-size account ID input.
|
||||
|
||||
This check warns unless the correct size is passed in the input size parameter (if it's specified by a constant - variable parameter is ignored).
|
||||
5
xrpl-hooks-docs/md/hooks-account-conv-pure.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# hooks-account-conv-pure
|
||||
|
||||
Hooks identify accounts by the 20 byte account ID, which can be converted to an raddr using the [util_raddr](https://xrpl-hooks.readme.io/v2.0/reference/util_raddr) function. If the account ID never changes, a more efficient way to do this is precompute the raddr from the account ID.
|
||||
|
||||
This check warns about calls of `util_raddr` with constant input and proposes to add a tracing statement showing the computed value (so that the user can use it to replace the call).
|
||||
7
xrpl-hooks-docs/md/hooks-array-buf-len.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# hooks-array-buf-len
|
||||
|
||||
Hook API [sto_subarray](https://xrpl-hooks.readme.io/v2.0/reference/sto_subarray) requires non-empty input buffer and takes a parameter specifying the array index, whose value is limited - the sought object cannot be found if the limit is exceeded.
|
||||
|
||||
This check warns about empty input as well as too-large values of the index specified in calls to `sto_subarray` (if they're specified by constants - variable parameters are ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/serialized-objects)
|
||||
3
xrpl-hooks-docs/md/hooks-burden-prereq.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# hooks-burden-prereq
|
||||
|
||||
Hook API [etxn_burden](https://xrpl-hooks.readme.io/v2.0/reference/etxn_burden) computes transaction burden, based on (i.a.) the number of reserved transactions, so a call to it must be preceded by a call to [etxn_reserve](https://xrpl-hooks.readme.io/v2.0/reference/etxn_reserve).
|
||||
7
xrpl-hooks-docs/md/hooks-detail-buf-len.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# hooks-detail-buf-len
|
||||
|
||||
Function [etxn_details](https://xrpl-hooks.readme.io/v2.0/reference/etxn_details) has fixed-size sfEmitDetails output.
|
||||
|
||||
This check warns about too-small size of its output buffer (if it's specified by a constant - variable parameter is ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/emitted-transactions)
|
||||
5
xrpl-hooks-docs/md/hooks-detail-prereq.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# hooks-detail-prereq
|
||||
|
||||
Hook API [etxn_details](https://xrpl-hooks.readme.io/v2.0/reference/etxn_details) serializes emit details, based on (i.a.) the number of reserved transactions, so a call to it must be preceded by a call to [etxn_reserve](https://xrpl-hooks.readme.io/v2.0/reference/etxn_reserve).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/emitted-transactions)
|
||||
7
xrpl-hooks-docs/md/hooks-emit-buf-len.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# hooks-emit-buf-len
|
||||
|
||||
Function [emit](https://xrpl-hooks.readme.io/v2.0/reference/emit) has fixed-size transaction hash output.
|
||||
|
||||
This check warns about too-small size of its output buffer (if it's specified by a constant - variable parameter is ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/emitted-transactions)
|
||||
5
xrpl-hooks-docs/md/hooks-emit-prereq.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# hooks-emit-prereq
|
||||
|
||||
Before emitting a transaction using [emit](https://xrpl-hooks.readme.io/v2.0/reference/emit) Hook API, a hook must set a maximal count of transactions it plans to emit, by calling [etxn_reserve](https://xrpl-hooks.readme.io/v2.0/reference/etxn_reserve).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/emitted-transactions)
|
||||
5
xrpl-hooks-docs/md/hooks-entry-point-recursion.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# hooks-entry-point-recursion
|
||||
|
||||
Recursive calls are disallowed in the implementation of hook entry points.
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/loops-and-guarding#no-recursion)
|
||||
5
xrpl-hooks-docs/md/hooks-entry-points-neg.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# hooks-entry-points-neg
|
||||
|
||||
Shows error on function definitions with unexpected (that is, neither `hook` nor `cbak`) names.
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/compiling-hooks#constraints)
|
||||
7
xrpl-hooks-docs/md/hooks-entry-points.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# hooks-entry-points
|
||||
|
||||
A Hook always implements and exports exactly two functions: [cbak](https://xrpl-hooks.readme.io/v2.0/reference/cbak) and [hook](https://xrpl-hooks.readme.io/v2.0/reference/hook).
|
||||
|
||||
This check shows error on translation units that do not have them.
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/compiling-hooks)
|
||||
5
xrpl-hooks-docs/md/hooks-fee-prereq.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# hooks-fee-prereq
|
||||
|
||||
Hook API [etxn_fee_base](https://xrpl-hooks.readme.io/v2.0/reference/etxn_fee_base) estimates a transaction fee, based on (i.a.) the number of reserved transactions, so a call to it must be preceded by a call to [etxn_reserve](https://xrpl-hooks.readme.io/v2.0/reference/etxn_reserve).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/hook-fees)
|
||||
7
xrpl-hooks-docs/md/hooks-field-add-buf-len.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# hooks-field-add-buf-len
|
||||
|
||||
Emplacing a new field into STObject by calling [sto_emplace](https://xrpl-hooks.readme.io/v2.0/reference/sto_emplace) requires enough space to serialize the new STObject into; the API also limits sizes of the old object and field.
|
||||
|
||||
This check warns about insufficient output buffer space as well as too-large values of the inputs in calls to `sto_emplace` (if they're specified by constants - variable parameters are ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/serialized-objects)
|
||||
7
xrpl-hooks-docs/md/hooks-field-buf-len.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# hooks-field-buf-len
|
||||
|
||||
Hook API [sto_subfield](https://xrpl-hooks.readme.io/v2.0/reference/sto_subfield) requires non-empty input buffer.
|
||||
|
||||
This check warns about empty input in calls to `sto_subfield` (if it's specified by a constant - variable parameter is ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/serialized-objects)
|
||||
7
xrpl-hooks-docs/md/hooks-field-del-buf-len.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# hooks-field-del-buf-len
|
||||
|
||||
Erasing a field from STObject by calling [sto_erase](https://xrpl-hooks.readme.io/v2.0/reference/sto_erase) requires enough space to serialize the new STObject into; the API also limits size of the old object.
|
||||
|
||||
This check warns about insufficient output buffer space as well as too-large value of the input STObject in calls to `sto_erase` (if they're specified by constants - variable parameters are ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/serialized-objects)
|
||||
8
xrpl-hooks-docs/md/hooks-float-arith-pure.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# hooks-float-arith-pure
|
||||
|
||||
Hooks can compute floating-point values in XFL format by calling functions [float_multiply](https://xrpl-hooks.readme.io/v2.0/reference/float_multiply), [float_mulratio](https://xrpl-hooks.readme.io/v2.0/reference/float_mulratio), [float_negate](https://xrpl-hooks.readme.io/v2.0/reference/float_negate), [float_sum](https://xrpl-hooks.readme.io/v2.0/reference/float_sum), [float_invert](https://xrpl-hooks.readme.io/v2.0/reference/float_invert) and [float_divide](https://xrpl-hooks.readme.io/v2.0/reference/float_divide) and access their constituent parts by calling [float_exponent](https://xrpl-hooks.readme.io/v2.0/reference/float_exponent), [float_mantissa](https://xrpl-hooks.readme.io/v2.0/reference/float_mantissa) and [float_sign](https://xrpl-hooks.readme.io/v2.0/reference/float_sign). If the inputs of the computation never change, a more efficient way to do this is to precompute it.
|
||||
|
||||
This check warns about calls of the aforementioned functions with constant inputs and in simple cases proposes to add a tracing statement showing the computed value (so that the user can use it to replace the call). It also checks that the divisor passed to `float_divide`, `float_mulratio` and `float_invert` is not 0 (if it's specified by a constant - variable parameters are ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/floating-point-numbers-xfl)
|
||||
|
||||
7
xrpl-hooks-docs/md/hooks-float-compare-pure.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# hooks-float-compare-pure
|
||||
|
||||
Hooks can compare floating-point values in XFL format by calling the [float_compare](https://xrpl-hooks.readme.io/v2.0/reference/float_compare) function. If the inputs of the comparison never change, its result is fixed and the function need not be called.
|
||||
|
||||
This check warns about calls of `float_compare` with constant inputs as well as invalid values of the comparison mode parameter (if it's specified by a constant - variable parameter is ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/floating-point-numbers-xfl)
|
||||
7
xrpl-hooks-docs/md/hooks-float-int-pure.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# hooks-float-int-pure
|
||||
|
||||
Hooks can convert floating-point values in XFL format to integers by calling the [float_int](https://xrpl-hooks.readme.io/v2.0/reference/float_int) function. If the inputs of this function never change, a more efficient way to do this is to precompute the integer value.
|
||||
|
||||
This check warns about calls of `float_int` with constant inputs as well as invalid values of the decimal places parameter (if it's specified by a constant - variable parameter is ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/floating-point-numbers-xfl)
|
||||
7
xrpl-hooks-docs/md/hooks-float-manip-pure.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# hooks-float-manip-pure
|
||||
|
||||
Hooks can directly manipulate floating-point values in XFL format by calling functions [float_exponent_set](https://xrpl-hooks.readme.io/v2.0/reference/float_exponent_set), [float_mantissa_set](https://xrpl-hooks.readme.io/v2.0/reference/float_mantissa_set) and [float_sign_set](https://xrpl-hooks.readme.io/v2.0/reference/float_sign_set). If the inputs of the update never change, a more efficient way to do this is to precompute it.
|
||||
|
||||
This check warns about calls of the aforementioned functions with constant inputs and in simple cases proposes to add a tracing statement showing the computed value (so that the user can use it to replace the call). It also checks documented bounds of the second parameter of these functions (if it's specified by a constant - variable parameter is ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/floating-point-numbers-xfl)
|
||||
5
xrpl-hooks-docs/md/hooks-float-one-pure.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# hooks-float-one-pure
|
||||
|
||||
Hooks can obtain XFL enclosing number 1 by calling the [float_one](https://xrpl-hooks.readme.io/v2.0/reference/float_one) function. Since the number never changes, a more efficient way is to use its precomputed value.
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/floating-point-numbers-xfl)
|
||||
7
xrpl-hooks-docs/md/hooks-float-pure.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# hooks-float-pure
|
||||
|
||||
Hooks can use floating-point values in XFL format, creating them from mantissa and exponent by calling the [float_set](https://xrpl-hooks.readme.io/v2.0/reference/float_set) function. If the mantissa and exponent never change, a more efficient way to do this is to precompute the floating-point value.
|
||||
|
||||
This check warns about calls of `float_set` with constant inputs and proposes to add a tracing statement showing the computed value (so that the user can use it to replace the call). In the special case of 0 mantissa and 0 exponent ("canonical 0"), a replacement value of 0 is proposed directly, with no need to trace it.
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/floating-point-numbers-xfl)
|
||||
5
xrpl-hooks-docs/md/hooks-guard-called.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# hooks-guard-called
|
||||
|
||||
Every hook needs to import the guard function [_g](https://xrpl-hooks.readme.io/v2.0/docs/loops-and-guarding#the-guard-function) and use it at least once.
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/loops-and-guarding)
|
||||
14
xrpl-hooks-docs/md/hooks-guard-in-for.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# hooks-guard-in-for
|
||||
|
||||
A guard is a marker that must be placed in your code at the top of each loop. Consider the following for-loop in C:
|
||||
|
||||
```c
|
||||
#define GUARD(maxiter) _g(__LINE__, (maxiter)+1)
|
||||
|
||||
for (int i = 0; GUARD(3), i < 3; ++i)
|
||||
```
|
||||
|
||||
<BR/>
|
||||
This is the only way to satisfy the guard rule when using a for-loop in C.
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/loops-and-guarding)
|
||||
14
xrpl-hooks-docs/md/hooks-guard-in-while.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# hooks-guard-in-while
|
||||
|
||||
Like for loops, while loops must have a guard in their condition:
|
||||
|
||||
```c
|
||||
|
||||
#define GUARD(maxiter) _g(__LINE__, (maxiter)+1)
|
||||
|
||||
int i = 0;
|
||||
while (GUARD(3), i < 3)
|
||||
```
|
||||
|
||||
<BR/>
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/loops-and-guarding)
|
||||
5
xrpl-hooks-docs/md/hooks-hash-buf-len.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# hooks-hash-buf-len
|
||||
|
||||
Functions [util_sha512h](https://xrpl-hooks.readme.io/v2.0/reference/util_sha512h), [hook_hash](https://xrpl-hooks.readme.io/v2.0/reference/hook_hash), [ledger_last_hash](https://xrpl-hooks.readme.io/v2.0/reference/ledger_last_hash) and [nonce](https://xrpl-hooks.readme.io/v2.0/reference/nonce) have fixed-size hash output.
|
||||
|
||||
This check warns about too-small size of their output buffer (if it's specified by a constant - variable parameter is ignored).
|
||||
7
xrpl-hooks-docs/md/hooks-keylet-buf-len.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# hooks-keylet-buf-len
|
||||
|
||||
Computing a ripple keylet by calling [util_keylet](https://xrpl-hooks.readme.io/v2.0/reference/util_keylet) requires valid parameters dependent on the keylet type.
|
||||
|
||||
This check does not fully parse these parameters, but warns about invalid keylet type as well as buffer sizes that cannot be valid (if they're specified by constants - variable parameters are ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/slots-and-keylets)
|
||||
7
xrpl-hooks-docs/md/hooks-param-buf-len.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# hooks-param-buf-len
|
||||
|
||||
Function [hook_param](https://xrpl-hooks.readme.io/v2.0/reference/hook_param) expects a limited-length name input and produces fixed-size value output.
|
||||
|
||||
This check warns about invalid sizes of input and output buffers (if they're specified by constants - variable parameters are ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/parameters)
|
||||
7
xrpl-hooks-docs/md/hooks-param-set-buf-len.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# hooks-param-set-buf-len
|
||||
|
||||
Function [hook_param_set](https://xrpl-hooks.readme.io/v2.0/reference/hook_param_set) expects limited-length name, fixed-length hash and limited-length value inputs.
|
||||
|
||||
This check warns about invalid sizes of input buffers (if they're specified by constants - variable parameters are ignored).
|
||||
|
||||
[Read more](https://xrpl-hooks.readme.io/v2.0/docs/parameters)
|
||||