Compare commits
291 Commits
feat/templ
...
feat/fee-e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4472957f5c | ||
|
|
9a6ef2c393 | ||
|
|
56203ce9c6 | ||
|
|
933bdb5968 | ||
|
|
864711697b | ||
|
|
e5eaf09721 | ||
|
|
d0dde56c67 | ||
|
|
45c6927e72 | ||
|
|
6014b6e79f | ||
|
|
04a99227df | ||
|
|
0965a1e898 | ||
|
|
32445dbebf | ||
|
|
1a1d4901aa | ||
|
|
8b646c56dc | ||
|
|
ac38bbc72c | ||
|
|
bf1182351a | ||
|
|
55e48a943b | ||
|
|
faf417be69 | ||
|
|
c2eb57211f | ||
|
|
0e97df3c8e | ||
|
|
5dd0dfdc18 | ||
|
|
ef48bac8f6 | ||
|
|
3a3d984098 | ||
|
|
2300c201f8 | ||
|
|
329dc4a355 | ||
|
|
cd6a5b23d4 | ||
|
|
4dd7cbe2ca | ||
|
|
260de7c838 | ||
|
|
e0ed31f220 | ||
|
|
eba183497f | ||
|
|
4378afa9a1 | ||
|
|
491e10920b | ||
|
|
65bb209713 | ||
|
|
c07e70acc9 | ||
|
|
8fd7f8ecad | ||
|
|
2bb3c646db | ||
|
|
87f10a11b0 | ||
|
|
949fb45ae2 | ||
|
|
ea52f014dd | ||
|
|
77eab8d88d | ||
|
|
4ca8f5f236 | ||
|
|
53f2a71b08 | ||
|
|
866f6257f1 | ||
|
|
814b074cc0 | ||
|
|
386619619b | ||
|
|
d8bf10d0b8 | ||
|
|
d18c893025 | ||
|
|
822f7a30f5 | ||
|
|
1d66137c23 | ||
|
|
4c42e75686 | ||
|
|
501b7fefec | ||
|
|
aa7e1517a2 | ||
|
|
e33093f160 | ||
|
|
923b689c98 | ||
|
|
246e7f137f | ||
|
|
5defd12a11 | ||
|
|
abb7c2bb28 | ||
|
|
12013907f8 | ||
|
|
ec75fff74b | ||
|
|
5e997044ed | ||
|
|
e88720327e | ||
|
|
bf568c3f46 | ||
|
|
7c1068449f | ||
|
|
b66d2a09a0 | ||
|
|
1d3bd128f8 | ||
|
|
ab1f45febd | ||
|
|
54265e024c | ||
|
|
20cb66ba18 | ||
|
|
56a9806b70 | ||
|
|
b3f2d0fb6d | ||
|
|
b7d62dda83 | ||
|
|
c690334f92 | ||
|
|
587f09ec00 | ||
|
|
9296ea1acc | ||
|
|
582fb17c94 | ||
|
|
aff0142870 | ||
|
|
df51d87cb2 | ||
|
|
6a46f5f173 | ||
|
|
9e25cefef9 | ||
|
|
95966fa514 | ||
|
|
f49d69e75d | ||
|
|
da4b2e68ab | ||
|
|
5557b1bcba | ||
|
|
f4b5f98a44 | ||
|
|
b1d39740de | ||
|
|
dfe5589074 | ||
|
|
cdc50da840 | ||
|
|
4893b41936 | ||
|
|
16cbdafb27 | ||
|
|
5559fb7be3 | ||
|
|
3c4305127b | ||
|
|
2a76fa0c35 | ||
|
|
bf21fe36c3 | ||
|
|
a33a3eb6e2 | ||
|
|
919c4e173c | ||
|
|
650324f434 | ||
|
|
74db96e8a5 | ||
|
|
c99c821081 | ||
|
|
e53a533026 | ||
|
|
5f118e00cb | ||
|
|
46e6927c68 | ||
|
|
de95a82c5a | ||
|
|
3b66d64c14 | ||
|
|
6e90a4c3d7 | ||
|
|
2287e7babb | ||
|
|
c219f7ea00 | ||
|
|
e795ce4472 | ||
|
|
04e2274dbf | ||
|
|
6e39b90c1e | ||
|
|
f186a807c1 | ||
|
|
5ad9ed1688 | ||
|
|
842b8a5226 | ||
|
|
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 | ||
|
|
195d33b1db | ||
|
|
4f042ef3b7 | ||
|
|
17c67822a9 | ||
|
|
e6f613ae0b | ||
|
|
9b822cfda4 | ||
|
|
b5b918d877 | ||
|
|
739918647d | ||
|
|
1f334d6253 | ||
|
|
0f15a85c45 | ||
|
|
0c4330e329 | ||
|
|
a9676288ea | ||
|
|
7354474c70 | ||
|
|
ce5b307a8b | ||
|
|
b28bcfdd0a | ||
|
|
7f06876e3e | ||
|
|
fd479d8671 | ||
|
|
938b567256 | ||
|
|
779f5aab0a | ||
|
|
02194d8a98 | ||
|
|
5677fe34dc | ||
|
|
895da89325 | ||
|
|
b138cc8d5b | ||
|
|
d85cc71817 | ||
|
|
bac3522078 | ||
|
|
b2c6aa7871 | ||
|
|
81e2a3673d |
@@ -2,4 +2,9 @@ NEXTAUTH_URL=https://example.com
|
|||||||
GITHUB_SECRET=""
|
GITHUB_SECRET=""
|
||||||
GITHUB_ID=""
|
GITHUB_ID=""
|
||||||
NEXT_PUBLIC_COMPILE_API_ENDPOINT="http://localhost:9000/api/build"
|
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
|
||||||
84
README.md
@@ -1,6 +1,8 @@
|
|||||||
# XRPL Hooks IDE
|
# XRPL Hooks Builder
|
||||||
|
|
||||||
This is the repository for XRPL Hooks IDE. This project is built with Next.JS
|
https://hooks-builder.xrpl.org/
|
||||||
|
|
||||||
|
This is the repository for XRPL Hooks Builder. This project is built with Next.JS
|
||||||
|
|
||||||
## General
|
## General
|
||||||
|
|
||||||
@@ -26,6 +28,78 @@ You can start editing the page by modifying `pages/index.tsx`. The page auto-upd
|
|||||||
|
|
||||||
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
|
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
|
||||||
|
|
||||||
|
## Github Login
|
||||||
|
|
||||||
|
If you want to use your Github app to provide login, here's the guide to do that.
|
||||||
|
|
||||||
|
- First go to https://github.com/settings/profile -> Developer Settings -> OAuth Apps
|
||||||
|
- Click "New OAuth App" button
|
||||||
|
- Give application some name eg. Xrpl Hooks Development
|
||||||
|
- Give some homepage url eg. localhost:3000
|
||||||
|
- Give some optional description (these values will show up on the popup when you login)
|
||||||
|
- Authorization callback URL should be http://localhost:3000/api/auth/callback (if you're creating the app for local development)
|
||||||
|
- Click register application
|
||||||
|
- Then a page should open up where you can get client id and client secret values. Copy paste those to .env.local to use them:
|
||||||
|
|
||||||
|
```
|
||||||
|
GITHUB_SECRET="client-secret-here"
|
||||||
|
GITHUB_ID="client-id-here"
|
||||||
|
```
|
||||||
|
|
||||||
|
Login should now work through your own Github OAuth app.
|
||||||
|
|
||||||
|
## Styling and Theming
|
||||||
|
|
||||||
|
This project uses Stitches (https://stitches.dev) for theming and styling the components. You should be quite familiar with the API if you have used for example styled-components earlier. Stitches should provide better performance, near zero runtime.
|
||||||
|
|
||||||
|
For components we try to use Radix-UI (https://www.radix-ui.com/) as much as possible. It may not provide all the necessary components so you're free to use other components/libraries if those makes sense. For colors we're using Radix-UI Colors (https://radix-ui.com/colors).
|
||||||
|
|
||||||
|
Theme file can be found under `./stitches.config.ts` file. When you're creating new components remeber to import `styled` from that file and not `@stitches` directly. That way it will provide the correct theme for you automatically.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Use our stitches.config instead of @stitches/react
|
||||||
|
import { styled } from "../stitches.config";
|
||||||
|
|
||||||
|
const Box = styled("div", {
|
||||||
|
boxSizing: "border-box",
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Box;
|
||||||
|
```
|
||||||
|
|
||||||
|
Custom components can be found from `./components` folder.
|
||||||
|
|
||||||
|
## Monaco Editor
|
||||||
|
|
||||||
|
Project is relying on Monaco editor heavily. Instead of using Monaco editor directly we're using `@monaco-editor/react` which provides little helpers to use Monaco editor.
|
||||||
|
|
||||||
|
On the Develop page we're using following loader for Monaco editor:
|
||||||
|
|
||||||
|
```js
|
||||||
|
loader.config({
|
||||||
|
paths: {
|
||||||
|
vs: "https://cdn.jsdelivr.net/npm/monaco-editor@0.30.1/min/vs",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
By default `@monaco-editor/react` was using 0.29.? version of Monaco editor. @codingame/monaco-languageclient library (connects to clangd language server) was using API methods that were introduced in Monaco Editor 0.30 so that's why we're loading certain version of it.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Special notes
|
||||||
|
|
||||||
|
Since we are dealing with greenfield tech and one of the dependencies (ripple-binary-codec) doesn't yet support signing `SetHook` transactions we had to monkey patch the library with patch-package (https://www.npmjs.com/package/patch-package). We modified the definitions.json file of the ripple-binary-codec library and then ran `yarn patch-package ripple-binary-codec` which created `patches/ripple-binary-codec+1.2.0.patch` file to this project. This file contains the modifications to `ripple-binary-codec` library. package.json contains postinstall hook which runs patch-package, and it will add the patch on the file mentioned earlier. This happens automatically after running patch package.
|
||||||
|
|
||||||
## Learn More
|
## Learn More
|
||||||
|
|
||||||
To learn more about Next.js, take a look at the following resources:
|
To learn more about Next.js, take a look at the following resources:
|
||||||
@@ -34,9 +108,3 @@ To learn more about Next.js, take a look at the following resources:
|
|||||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||||
|
|
||||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||||
|
|
||||||
## Deploy on Vercel
|
|
||||||
|
|
||||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
|
||||||
|
|
||||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
|
||||||
|
|||||||
@@ -1,18 +1,14 @@
|
|||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { useSnapshot } from "valtio";
|
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 } from "react";
|
import React, { useEffect, useState, FC } from "react";
|
||||||
import Dinero from "dinero.js";
|
import Dinero from "dinero.js";
|
||||||
|
|
||||||
import Button from "./Button";
|
import Button from "./Button";
|
||||||
import { addFaucetAccount, deployHook, importAccount } from "../state/actions";
|
import { addFaucetAccount, importAccount } from "../state/actions";
|
||||||
import state from "../state";
|
import state from "../state";
|
||||||
import Box from "./Box";
|
import Box from "./Box";
|
||||||
import Container from "./Container";
|
import { Container, Heading, Stack, Text, Flex } from ".";
|
||||||
import Heading from "./Heading";
|
|
||||||
import Stack from "./Stack";
|
|
||||||
import Text from "./Text";
|
|
||||||
import Flex from "./Flex";
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -22,7 +18,8 @@ import {
|
|||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "./Dialog";
|
} from "./Dialog";
|
||||||
import { css } from "../stitches.config";
|
import { css } from "../stitches.config";
|
||||||
import { Input } from "./Input";
|
import { Input, Label } from "./Input";
|
||||||
|
import truncate from "../utils/truncate";
|
||||||
|
|
||||||
const labelStyle = css({
|
const labelStyle = css({
|
||||||
color: "$mauve10",
|
color: "$mauve10",
|
||||||
@@ -30,8 +27,12 @@ const labelStyle = css({
|
|||||||
fontSize: "10px",
|
fontSize: "10px",
|
||||||
mb: "$0.5",
|
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,
|
activeAccountAddress,
|
||||||
setActiveAccountAddress,
|
setActiveAccountAddress,
|
||||||
}: {
|
}: {
|
||||||
@@ -90,6 +91,22 @@ const AccountDialog = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Wallet size="15px" /> {activeAccount?.name}
|
<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>
|
</DialogTitle>
|
||||||
<DialogDescription as="div" css={{ fontFamily: "$monospace" }}>
|
<DialogDescription as="div" css={{ fontFamily: "$monospace" }}>
|
||||||
<Stack css={{ display: "flex", flexDirection: "column", gap: "$3" }}>
|
<Stack css={{ display: "flex", flexDirection: "column", gap: "$3" }}>
|
||||||
@@ -167,6 +184,8 @@ const AccountDialog = ({
|
|||||||
<Text
|
<Text
|
||||||
css={{
|
css={{
|
||||||
fontFamily: "$monospace",
|
fontFamily: "$monospace",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Dinero({
|
{Dinero({
|
||||||
@@ -179,18 +198,33 @@ const AccountDialog = ({
|
|||||||
currency: "XRP",
|
currency: "XRP",
|
||||||
currencyDisplay: "name",
|
currencyDisplay: "name",
|
||||||
})}
|
})}
|
||||||
|
<Button
|
||||||
|
css={{
|
||||||
|
fontFamily: "$monospace",
|
||||||
|
lineHeight: 2,
|
||||||
|
mt: "2px",
|
||||||
|
ml: "$3",
|
||||||
|
}}
|
||||||
|
ghost
|
||||||
|
size="xs"
|
||||||
|
onClick={() => {
|
||||||
|
addFunds(activeAccount?.address || "");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add Funds
|
||||||
|
</Button>
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex css={{ marginLeft: "auto" }}>
|
<Flex css={{ marginLeft: "auto" }}>
|
||||||
<a
|
<a
|
||||||
href={`https://hooks-testnet-explorer.xrpl-labs.com/${activeAccount?.address}`}
|
href={`https://${process.env.NEXT_PUBLIC_EXPLORER_URL}/${activeAccount?.address}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
ghost
|
ghost
|
||||||
css={{ color: "$green11 !important", mt: "$3" }}
|
css={{ color: "$grass11 !important", mt: "$3" }}
|
||||||
>
|
>
|
||||||
<ArrowSquareOut size="15px" />
|
<ArrowSquareOut size="15px" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -205,17 +239,26 @@ const AccountDialog = ({
|
|||||||
fontFamily: "$monospace",
|
fontFamily: "$monospace",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{activeAccount && activeAccount?.hooks?.length > 0
|
{activeAccount && activeAccount.hooks.length > 0
|
||||||
? activeAccount?.hooks
|
? activeAccount.hooks.map((i) => truncate(i, 12)).join(",")
|
||||||
.map((i) => {
|
|
||||||
return `${i?.substring(0, 6)}...${i?.substring(
|
|
||||||
i.length - 4
|
|
||||||
)}`;
|
|
||||||
})
|
|
||||||
.join(", ")
|
|
||||||
: "–"}
|
: "–"}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</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>
|
</Flex>
|
||||||
</Stack>
|
</Stack>
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
@@ -229,7 +272,13 @@ const AccountDialog = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Accounts = () => {
|
interface AccountProps {
|
||||||
|
card?: boolean;
|
||||||
|
hideDeployBtn?: boolean;
|
||||||
|
showHookStats?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Accounts: FC<AccountProps> = (props) => {
|
||||||
const snap = useSnapshot(state);
|
const snap = useSnapshot(state);
|
||||||
const [activeAccountAddress, setActiveAccountAddress] = useState<
|
const [activeAccountAddress, setActiveAccountAddress] = useState<
|
||||||
string | null
|
string | null
|
||||||
@@ -239,7 +288,7 @@ const Accounts = () => {
|
|||||||
if (snap.clientStatus === "online") {
|
if (snap.clientStatus === "online") {
|
||||||
const requests = snap.accounts.map((acc) =>
|
const requests = snap.accounts.map((acc) =>
|
||||||
snap.client?.send({
|
snap.client?.send({
|
||||||
id: acc.address,
|
id: `hooks-builder-req-info-${acc.address}`,
|
||||||
command: "account_info",
|
command: "account_info",
|
||||||
account: acc.address,
|
account: acc.address,
|
||||||
})
|
})
|
||||||
@@ -255,11 +304,23 @@ const Accounts = () => {
|
|||||||
if (accountToUpdate) {
|
if (accountToUpdate) {
|
||||||
accountToUpdate.xrp = balance;
|
accountToUpdate.xrp = balance;
|
||||||
accountToUpdate.sequence = sequence;
|
accountToUpdate.sequence = sequence;
|
||||||
|
accountToUpdate.error = null;
|
||||||
|
} else {
|
||||||
|
const oldAccount = state.accounts.find(
|
||||||
|
(acc) => acc.address === res?.account
|
||||||
|
);
|
||||||
|
if (oldAccount) {
|
||||||
|
oldAccount.xrp = "0";
|
||||||
|
oldAccount.error = {
|
||||||
|
code: res?.error,
|
||||||
|
message: res?.error_message,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const objectRequests = snap.accounts.map((acc) => {
|
const objectRequests = snap.accounts.map((acc) => {
|
||||||
return snap.client?.send({
|
return snap.client?.send({
|
||||||
id: `${acc.address}-hooks`,
|
id: `hooks-builder-req-objects-${acc.address}`,
|
||||||
command: "account_objects",
|
command: "account_objects",
|
||||||
account: acc.address,
|
account: acc.address,
|
||||||
});
|
});
|
||||||
@@ -271,9 +332,10 @@ const Accounts = () => {
|
|||||||
(acc) => acc.address === address
|
(acc) => acc.address === address
|
||||||
);
|
);
|
||||||
if (accountToUpdate) {
|
if (accountToUpdate) {
|
||||||
accountToUpdate.hooks = res.account_objects
|
accountToUpdate.hooks =
|
||||||
.filter((ac: any) => ac?.LedgerEntryType === "Hook")
|
res.account_objects
|
||||||
.map((oo: any) => oo.HookHash);
|
.find((ac: any) => ac?.LedgerEntryType === "Hook")
|
||||||
|
?.Hooks?.map((oo: any) => oo.Hook.HookHash) || [];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -282,7 +344,7 @@ const Accounts = () => {
|
|||||||
let fetchAccountInfoInterval: NodeJS.Timer;
|
let fetchAccountInfoInterval: NodeJS.Timer;
|
||||||
if (snap.clientStatus === "online") {
|
if (snap.clientStatus === "online") {
|
||||||
fetchAccInfo();
|
fetchAccInfo();
|
||||||
fetchAccountInfoInterval = setInterval(() => fetchAccInfo(), 2000);
|
fetchAccountInfoInterval = setInterval(() => fetchAccInfo(), 10000);
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@@ -293,22 +355,27 @@ const Accounts = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [snap.accounts, snap.clientStatus]);
|
}, [snap.accounts.length, snap.clientStatus]);
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
as="div"
|
as="div"
|
||||||
css={{
|
css={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
borderTop: "1px solid $mauve6",
|
backgroundColor: props.card ? "$deep" : "$mauve1",
|
||||||
background: "$mauve1",
|
|
||||||
position: "relative",
|
position: "relative",
|
||||||
width: "50%",
|
flex: "1",
|
||||||
flexShrink: 0,
|
height: "100%",
|
||||||
borderRight: "1px solid $mauve6",
|
border: "1px solid $mauve6",
|
||||||
|
borderRadius: props.card ? "$md" : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Container css={{ px: 0, flexShrink: 1 }}>
|
<Container css={{ p: 0, flexShrink: 1, height: "100%" }}>
|
||||||
<Flex css={{ py: "$3" }}>
|
<Flex
|
||||||
|
css={{
|
||||||
|
py: "$3",
|
||||||
|
borderBottom: props.card ? "1px solid $mauve6" : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Heading
|
<Heading
|
||||||
as="h3"
|
as="h3"
|
||||||
css={{
|
css={{
|
||||||
@@ -326,79 +393,95 @@ const Accounts = () => {
|
|||||||
<Wallet size="15px" /> <Text css={{ lineHeight: 1 }}>Accounts</Text>
|
<Wallet size="15px" /> <Text css={{ lineHeight: 1 }}>Accounts</Text>
|
||||||
</Heading>
|
</Heading>
|
||||||
<Flex css={{ ml: "auto", gap: "$3", marginRight: "$3" }}>
|
<Flex css={{ ml: "auto", gap: "$3", marginRight: "$3" }}>
|
||||||
<Button ghost size="xs" onClick={() => addFaucetAccount(true)}>
|
<Button ghost size="sm" onClick={() => addFaucetAccount(true)}>
|
||||||
Create
|
Create
|
||||||
</Button>
|
</Button>
|
||||||
<ImportAccountDialog />
|
<ImportAccountDialog />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Box
|
<Stack
|
||||||
as="div"
|
|
||||||
css={{
|
css={{
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "160px",
|
|
||||||
fontSize: "13px",
|
fontSize: "13px",
|
||||||
fontWeight: "$body",
|
|
||||||
fontFamily: "$monospace",
|
|
||||||
overflowY: "auto",
|
|
||||||
wordWrap: "break-word",
|
wordWrap: "break-word",
|
||||||
|
fontWeight: "$body",
|
||||||
|
gap: 0,
|
||||||
|
height: "calc(100% - 52px)",
|
||||||
|
flexWrap: "nowrap",
|
||||||
|
overflowY: "auto",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack css={{ flexDirection: "column", gap: 0 }}>
|
{snap.accounts.map((account) => (
|
||||||
{snap.accounts.map((account) => (
|
<Flex
|
||||||
<Flex
|
column
|
||||||
key={account.address + account.name}
|
key={account.address + account.name}
|
||||||
onClick={() => setActiveAccountAddress(account.address)}
|
onClick={() => setActiveAccountAddress(account.address)}
|
||||||
css={{
|
css={{
|
||||||
gap: "$3",
|
px: "$3",
|
||||||
p: "$2 $3",
|
py: props.card ? "$3" : "$2",
|
||||||
justifyContent: "center",
|
cursor: "pointer",
|
||||||
cursor: "pointer",
|
borderBottom: props.card ? "1px solid $mauve6" : undefined,
|
||||||
"@hover": {
|
"@hover": {
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
background: "$mauve3",
|
background: "$backgroundAlt",
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
row
|
||||||
|
css={{
|
||||||
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text>{account.name} </Text>
|
<Box>
|
||||||
<Text css={{ color: "$mauve9" }}>
|
<Text>{account.name} </Text>
|
||||||
{account.address} (
|
<Text
|
||||||
{Dinero({
|
css={{
|
||||||
amount: Number(account?.xrp || "0"),
|
color: "$textMuted",
|
||||||
precision: 6,
|
wordBreak: "break-word",
|
||||||
})
|
}}
|
||||||
.toUnit()
|
>
|
||||||
.toLocaleString(undefined, {
|
{account.address}{" "}
|
||||||
style: "currency",
|
{!account?.error ? (
|
||||||
currency: "XRP",
|
`(${Dinero({
|
||||||
currencyDisplay: "name",
|
amount: Number(account?.xrp || "0"),
|
||||||
})}
|
precision: 6,
|
||||||
)
|
})
|
||||||
</Text>
|
.toUnit()
|
||||||
<Button
|
.toLocaleString(undefined, {
|
||||||
css={{ ml: "auto" }}
|
style: "currency",
|
||||||
size="xs"
|
currency: "XRP",
|
||||||
uppercase
|
currencyDisplay: "name",
|
||||||
isLoading={account.isLoading}
|
})})`
|
||||||
disabled={
|
) : (
|
||||||
account.isLoading ||
|
<Box css={{ color: "$red11" }}>
|
||||||
!snap.files.filter((file) => file.compiledWatContent).length
|
(Account not found, request funds to activate account)
|
||||||
}
|
</Box>
|
||||||
variant="secondary"
|
)}
|
||||||
onClick={(e) => {
|
</Text>
|
||||||
e.stopPropagation();
|
</Box>
|
||||||
deployHook(account);
|
{!props.hideDeployBtn && (
|
||||||
}}
|
<div
|
||||||
>
|
className="hook-deploy-button"
|
||||||
Deploy
|
onClick={(e) => {
|
||||||
</Button>
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SetHookDialog accountAddress={account.address} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
))}
|
{props.showHookStats && (
|
||||||
</Stack>
|
<Text muted small css={{ mt: "$2" }}>
|
||||||
</Box>
|
{account.hooks.length} hook
|
||||||
|
{account.hooks.length === 1 ? "" : "s"} installed
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
</Container>
|
</Container>
|
||||||
<AccountDialog
|
<AccountDialog
|
||||||
activeAccountAddress={activeAccountAddress}
|
activeAccountAddress={activeAccountAddress}
|
||||||
@@ -408,19 +491,24 @@ const Accounts = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const transactionsOptions = transactionsData.map((tx) => ({
|
||||||
|
value: tx.TransactionType,
|
||||||
|
label: tx.TransactionType,
|
||||||
|
}));
|
||||||
|
|
||||||
const ImportAccountDialog = () => {
|
const ImportAccountDialog = () => {
|
||||||
const [value, setValue] = useState("");
|
const [value, setValue] = useState("");
|
||||||
return (
|
return (
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button ghost size="xs">
|
<Button ghost size="sm">
|
||||||
Import
|
Import
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogTitle>Import account</DialogTitle>
|
<DialogTitle>Import account</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
<label>Add account secret</label>
|
<Label>Add account secret</Label>
|
||||||
<Input
|
<Input
|
||||||
name="secret"
|
name="secret"
|
||||||
type="password"
|
type="password"
|
||||||
|
|||||||
75
components/AlertDialog/index.tsx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { FC, ReactNode } from "react";
|
||||||
|
import { proxy, useSnapshot } from "valtio";
|
||||||
|
import Button from "../Button";
|
||||||
|
import Flex from "../Flex";
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from "./primitive";
|
||||||
|
|
||||||
|
export interface AlertState {
|
||||||
|
isOpen: boolean;
|
||||||
|
title?: string;
|
||||||
|
body?: ReactNode;
|
||||||
|
cancelText?: string;
|
||||||
|
confirmText?: string;
|
||||||
|
confirmPrefix?: ReactNode;
|
||||||
|
onConfirm?: () => any;
|
||||||
|
onCancel?: () => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const alertState = proxy<AlertState>({
|
||||||
|
isOpen: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const Alert: FC = () => {
|
||||||
|
const {
|
||||||
|
title = "Are you sure?",
|
||||||
|
isOpen,
|
||||||
|
body,
|
||||||
|
cancelText,
|
||||||
|
confirmText = "Ok",
|
||||||
|
confirmPrefix,
|
||||||
|
onCancel,
|
||||||
|
onConfirm,
|
||||||
|
} = useSnapshot(alertState);
|
||||||
|
return (
|
||||||
|
<AlertDialog
|
||||||
|
open={isOpen}
|
||||||
|
onOpenChange={value => (alertState.isOpen = value)}
|
||||||
|
>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogTitle>{title}</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>{body}</AlertDialogDescription>
|
||||||
|
<Flex css={{ justifyContent: "flex-end", gap: "$3" }}>
|
||||||
|
{(cancelText || onCancel) && (
|
||||||
|
<AlertDialogCancel asChild>
|
||||||
|
<Button css={{ minWidth: "$16" }} outline onClick={onCancel}>
|
||||||
|
{cancelText || "Cancel"}
|
||||||
|
</Button>
|
||||||
|
</AlertDialogCancel>
|
||||||
|
)}
|
||||||
|
<AlertDialogAction asChild>
|
||||||
|
<Button
|
||||||
|
css={{ minWidth: "$16" }}
|
||||||
|
variant="primary"
|
||||||
|
onClick={async () => {
|
||||||
|
await onConfirm?.();
|
||||||
|
alertState.isOpen = false;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{confirmPrefix}
|
||||||
|
{confirmText}
|
||||||
|
</Button>
|
||||||
|
</AlertDialogAction>
|
||||||
|
</Flex>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Alert;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { blackA } from "@radix-ui/colors";
|
import { blackA } from "@radix-ui/colors";
|
||||||
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
|
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
|
||||||
import { styled, keyframes } from "../stitches.config";
|
import { styled, keyframes } from "../../stitches.config";
|
||||||
|
|
||||||
const overlayShow = keyframes({
|
const overlayShow = keyframes({
|
||||||
"0%": { opacity: 0 },
|
"0%": { opacity: 0 },
|
||||||
@@ -75,7 +75,7 @@ const StyledDescription = styled(AlertDialogPrimitive.Description, {
|
|||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
color: "$mauve11",
|
color: "$mauve11",
|
||||||
lineHeight: 1.5,
|
lineHeight: 1.5,
|
||||||
fontSize: "$sm",
|
fontSize: "$md",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Exports
|
// Exports
|
||||||
@@ -66,6 +66,12 @@ export const StyledButton = styled("button", {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
variant: {
|
variant: {
|
||||||
|
link: {
|
||||||
|
textDecoration: "underline",
|
||||||
|
fontSize: "inherit",
|
||||||
|
color: "$textMuted",
|
||||||
|
textUnderlineOffset: "2px",
|
||||||
|
},
|
||||||
default: {
|
default: {
|
||||||
backgroundColor: "$mauve12",
|
backgroundColor: "$mauve12",
|
||||||
boxShadow: "inset 0 0 0 1px $colors$mauve12",
|
boxShadow: "inset 0 0 0 1px $colors$mauve12",
|
||||||
@@ -91,26 +97,26 @@ export const StyledButton = styled("button", {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
primary: {
|
primary: {
|
||||||
backgroundColor: `$pink9`,
|
backgroundColor: `$accent`,
|
||||||
boxShadow: "inset 0 0 0 1px $colors$pink9",
|
boxShadow: "inset 0 0 0 1px $colors$purple9",
|
||||||
color: "$white",
|
color: "$white",
|
||||||
"@hover": {
|
"@hover": {
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: "$pink10",
|
backgroundColor: "$purple10",
|
||||||
boxShadow: "inset 0 0 0 1px $colors$pink11",
|
boxShadow: "inset 0 0 0 1px $colors$purple11",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"&:active": {
|
"&:active": {
|
||||||
backgroundColor: "$pink8",
|
backgroundColor: "$purple8",
|
||||||
boxShadow: "inset 0 0 0 1px $colors$pink8",
|
boxShadow: "inset 0 0 0 1px $colors$purple8",
|
||||||
},
|
},
|
||||||
"&:focus": {
|
"&: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"]':
|
'&[data-radix-popover-trigger][data-state="open"], &[data-radix-dropdown-menu-trigger][data-state="open"]':
|
||||||
{
|
{
|
||||||
backgroundColor: "$mauve4",
|
backgroundColor: "$mauve4",
|
||||||
boxShadow: "inset 0 0 0 1px $colors$pink8",
|
boxShadow: "inset 0 0 0 1px $colors$purple8",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
@@ -136,8 +142,45 @@ export const StyledButton = styled("button", {
|
|||||||
boxShadow: "inset 0 0 0 1px $colors$purple8",
|
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: {
|
outline: {
|
||||||
true: {
|
true: {
|
||||||
backgroundColor: "transparent",
|
backgroundColor: "transparent",
|
||||||
|
|||||||
217
components/DebugStream.tsx
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
import { useCallback, useEffect } from "react";
|
||||||
|
import { proxy, ref, useSnapshot } from "valtio";
|
||||||
|
import { Select } from ".";
|
||||||
|
import state, { ILog, transactionsState } from "../state";
|
||||||
|
import { extractJSON } from "../utils/json";
|
||||||
|
import LogBox from "./LogBox";
|
||||||
|
|
||||||
|
interface ISelect<T = string> {
|
||||||
|
label: string;
|
||||||
|
value: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IStreamState {
|
||||||
|
selectedAccount: ISelect | null;
|
||||||
|
status: "idle" | "opened" | "closed";
|
||||||
|
statusChangeTimestamp?: number;
|
||||||
|
logs: ILog[];
|
||||||
|
socket?: WebSocket;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const streamState = proxy<IStreamState>({
|
||||||
|
selectedAccount: null as ISelect | null,
|
||||||
|
status: "idle",
|
||||||
|
logs: [] as ILog[],
|
||||||
|
});
|
||||||
|
|
||||||
|
const DebugStream = () => {
|
||||||
|
const { selectedAccount, logs, socket } = useSnapshot(streamState);
|
||||||
|
const { activeHeader: activeTxTab } = useSnapshot(transactionsState);
|
||||||
|
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%" }}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
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]);
|
||||||
|
|
||||||
|
const onMount = useCallback(async () => {
|
||||||
|
// deliberately using `proxy` values and not the `useSnapshot` ones to have no dep list
|
||||||
|
const acc = streamState.selectedAccount;
|
||||||
|
const status = streamState.status;
|
||||||
|
|
||||||
|
if (status === "opened" && acc) {
|
||||||
|
// fetch the missing ones
|
||||||
|
try {
|
||||||
|
const url = `https://${process.env.NEXT_PUBLIC_DEBUG_STREAM_URL}/recent/${acc?.value}`;
|
||||||
|
|
||||||
|
// TODO Remove after api sets cors properly
|
||||||
|
const res = await fetch("/api/proxy", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({ url }),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) return;
|
||||||
|
|
||||||
|
const body = await res.json();
|
||||||
|
|
||||||
|
if (!body?.logs) return;
|
||||||
|
|
||||||
|
const start = streamState.statusChangeTimestamp || 0;
|
||||||
|
streamState.logs = [];
|
||||||
|
pushLog(`Debug stream opened for account ${acc.value}`, {
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
const logs = Object.entries(body.logs).filter(([tm]) => +tm >= start);
|
||||||
|
|
||||||
|
logs.forEach(([tm, log]) => pushLog(log));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onMount();
|
||||||
|
}, [onMount]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const account = selectedAccount?.value;
|
||||||
|
const socket = streamState.socket;
|
||||||
|
if (!socket) return;
|
||||||
|
|
||||||
|
const onOpen = () => {
|
||||||
|
streamState.logs = [];
|
||||||
|
streamState.status = "opened";
|
||||||
|
streamState.statusChangeTimestamp = Date.now();
|
||||||
|
pushLog(`Debug stream opened for account ${account}`, {
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const onError = () => {
|
||||||
|
pushLog("Something went wrong! Check your connection and try again.", {
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const onClose = (e: CloseEvent) => {
|
||||||
|
pushLog(`Connection was closed. [code: ${e.code}]`, {
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
streamState.selectedAccount = null;
|
||||||
|
streamState.status = "closed";
|
||||||
|
streamState.statusChangeTimestamp = Date.now();
|
||||||
|
};
|
||||||
|
const onMessage = (event: any) => {
|
||||||
|
pushLog(event.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
}, [selectedAccount?.value, socket]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const account = transactionsState.transactions.find(
|
||||||
|
tx => tx.header === activeTxTab
|
||||||
|
)?.state.selectedAccount;
|
||||||
|
|
||||||
|
if (account && account.value !== streamState.selectedAccount?.value)
|
||||||
|
streamState.selectedAccount = account;
|
||||||
|
}, [activeTxTab]);
|
||||||
|
|
||||||
|
const clearLog = () => {
|
||||||
|
streamState.logs = [];
|
||||||
|
streamState.statusChangeTimestamp = Date.now();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LogBox
|
||||||
|
enhanced
|
||||||
|
renderNav={renderNav}
|
||||||
|
title="Debug stream"
|
||||||
|
logs={logs}
|
||||||
|
clearLog={clearLog}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DebugStream;
|
||||||
|
|
||||||
|
export const pushLog = (
|
||||||
|
str: any,
|
||||||
|
opts: Partial<Pick<ILog, "type">> = {}
|
||||||
|
): ILog | undefined => {
|
||||||
|
if (!str) return;
|
||||||
|
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 timestamp = Date.parse(tm || "") || undefined;
|
||||||
|
const timestring = !timestamp ? tm : new Date(timestamp).toLocaleTimeString();
|
||||||
|
|
||||||
|
const extracted = extractJSON(msg);
|
||||||
|
const message = !extracted
|
||||||
|
? msg
|
||||||
|
: msg.slice(0, extracted.start) + msg.slice(extracted.end + 1);
|
||||||
|
|
||||||
|
const jsonData = extracted
|
||||||
|
? JSON.stringify(extracted.result, null, 2)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (extracted?.result?.id?._Request?.includes("hooks-builder-req")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { type = "log" } = opts;
|
||||||
|
const log: ILog = {
|
||||||
|
type,
|
||||||
|
message,
|
||||||
|
timestring,
|
||||||
|
jsonData,
|
||||||
|
defaultCollapsed: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (log) streamState.logs.push(log);
|
||||||
|
return log;
|
||||||
|
};
|
||||||
@@ -1,20 +1,22 @@
|
|||||||
import React, { useRef } from "react";
|
import React, { useRef, useState } from "react";
|
||||||
import { useSnapshot, ref } from "valtio";
|
import { useSnapshot, ref } from "valtio";
|
||||||
import Editor, { loader } from "@monaco-editor/react";
|
import Editor, { loader } from "@monaco-editor/react";
|
||||||
import type monaco from "monaco-editor";
|
import type monaco from "monaco-editor";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import NextLink from "next/link";
|
import NextLink from "next/link";
|
||||||
|
import ReactTimeAgo from "react-time-ago";
|
||||||
|
import filesize from "filesize";
|
||||||
|
|
||||||
import Box from "./Box";
|
import Box from "./Box";
|
||||||
import Container from "./Container";
|
import Container from "./Container";
|
||||||
import dark from "../theme/editor/amy.json";
|
import dark from "../theme/editor/amy.json";
|
||||||
import light from "../theme/editor/xcode_default.json";
|
import light from "../theme/editor/xcode_default.json";
|
||||||
import state from "../state";
|
import state from "../state";
|
||||||
|
import wat from "../utils/wat-highlight";
|
||||||
|
|
||||||
import EditorNavigation from "./EditorNavigation";
|
import EditorNavigation from "./EditorNavigation";
|
||||||
import Text from "./Text";
|
import { Button, Text, Link, Flex } from ".";
|
||||||
import Link from "./Link";
|
|
||||||
|
|
||||||
loader.config({
|
loader.config({
|
||||||
paths: {
|
paths: {
|
||||||
@@ -22,11 +24,72 @@ loader.config({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const FILESIZE_BREAKPOINTS: [number, number] = [2 * 1024, 5 * 1024];
|
||||||
|
|
||||||
const DeployEditor = () => {
|
const DeployEditor = () => {
|
||||||
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>();
|
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>();
|
||||||
const snap = useSnapshot(state);
|
const snap = useSnapshot(state);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { theme } = useTheme();
|
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 (
|
return (
|
||||||
<Box
|
<Box
|
||||||
css={{
|
css={{
|
||||||
@@ -34,65 +97,53 @@ const DeployEditor = () => {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
backgroundColor: "$mauve3",
|
backgroundColor: "$mauve2",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<EditorNavigation showWat />
|
<EditorNavigation showWat />
|
||||||
{snap.files?.filter((file) => file.compiledWatContent).length > 0 &&
|
<Container
|
||||||
router.isReady ? (
|
css={{
|
||||||
<Editor
|
display: "flex",
|
||||||
className="hooks-editor"
|
flex: 1,
|
||||||
// keepCurrentModel
|
justifyContent: "center",
|
||||||
defaultLanguage={snap.files?.[snap.active]?.language}
|
alignItems: "center",
|
||||||
language={snap.files?.[snap.active]?.language}
|
}}
|
||||||
path={`file://tmp/c/${snap.files?.[snap.active]?.name}.wat`}
|
>
|
||||||
value={snap.files?.[snap.active]?.compiledWatContent || ""}
|
{!isContent ? (
|
||||||
beforeMount={(monaco) => {
|
NoContentView
|
||||||
if (!state.editorCtx) {
|
) : !showContent ? (
|
||||||
state.editorCtx = ref(monaco.editor);
|
CompiledStatView
|
||||||
// @ts-expect-error
|
) : (
|
||||||
monaco.editor.defineTheme("dark", dark);
|
<Editor
|
||||||
// @ts-expect-error
|
className="hooks-editor"
|
||||||
monaco.editor.defineTheme("light", light);
|
defaultLanguage={"wat"}
|
||||||
}
|
language={"wat"}
|
||||||
}}
|
path={`file://tmp/c/${snap.files?.[snap.active]?.name}.wat`}
|
||||||
onMount={(editor, monaco) => {
|
value={snap.files?.[snap.active]?.compiledWatContent || ""}
|
||||||
editorRef.current = editor;
|
beforeMount={(monaco) => {
|
||||||
editor.updateOptions({
|
monaco.languages.register({ id: "wat" });
|
||||||
glyphMargin: true,
|
monaco.languages.setLanguageConfiguration("wat", wat.config);
|
||||||
readOnly: true,
|
monaco.languages.setMonarchTokensProvider("wat", wat.tokens);
|
||||||
});
|
if (!state.editorCtx) {
|
||||||
}}
|
state.editorCtx = ref(monaco.editor);
|
||||||
theme={theme === "dark" ? "dark" : "light"}
|
// @ts-expect-error
|
||||||
/>
|
monaco.editor.defineTheme("dark", dark);
|
||||||
) : (
|
// @ts-expect-error
|
||||||
<Container
|
monaco.editor.defineTheme("light", light);
|
||||||
css={{
|
}
|
||||||
display: "flex",
|
}}
|
||||||
flex: 1,
|
onMount={(editor, monaco) => {
|
||||||
justifyContent: "center",
|
editorRef.current = editor;
|
||||||
alignItems: "center",
|
editor.updateOptions({
|
||||||
}}
|
glyphMargin: true,
|
||||||
>
|
readOnly: true,
|
||||||
{!snap.loading && router.isReady && (
|
});
|
||||||
<Text
|
}}
|
||||||
css={{
|
theme={theme === "dark" ? "dark" : "light"}
|
||||||
mt: "-60px",
|
/>
|
||||||
fontSize: "14px",
|
)}
|
||||||
fontFamily: "$monospace",
|
</Container>
|
||||||
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>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,21 +6,28 @@ import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|||||||
import { styled } from "../stitches.config";
|
import { styled } from "../stitches.config";
|
||||||
|
|
||||||
const overlayShow = keyframes({
|
const overlayShow = keyframes({
|
||||||
"0%": { opacity: 0 },
|
"0%": { opacity: 0.01 },
|
||||||
"100%": { opacity: 1 },
|
"100%": { opacity: 1 },
|
||||||
});
|
});
|
||||||
|
|
||||||
const contentShow = keyframes({
|
const contentShow = keyframes({
|
||||||
"0%": { opacity: 0, transform: "translate(-50%, -48%) scale(.96)" },
|
"0%": { opacity: 0.01 },
|
||||||
"100%": { opacity: 1, transform: "translate(-50%, -50%) scale(1)" },
|
"100%": { opacity: 1 },
|
||||||
});
|
});
|
||||||
const StyledOverlay = styled(DialogPrimitive.Overlay, {
|
const StyledOverlay = styled(DialogPrimitive.Overlay, {
|
||||||
zIndex: 1000,
|
zIndex: 9999,
|
||||||
backgroundColor: blackA.blackA9,
|
backgroundColor: blackA.blackA9,
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
inset: 0,
|
inset: 0,
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
display: "grid",
|
||||||
|
placeItems: "center",
|
||||||
|
overflowY: "auto",
|
||||||
"@media (prefers-reduced-motion: no-preference)": {
|
"@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 &": {
|
".dark &": {
|
||||||
backgroundColor: blackA.blackA11,
|
backgroundColor: blackA.blackA11,
|
||||||
@@ -32,15 +39,13 @@ const StyledContent = styled(DialogPrimitive.Content, {
|
|||||||
backgroundColor: "$mauve2",
|
backgroundColor: "$mauve2",
|
||||||
color: "$mauve12",
|
color: "$mauve12",
|
||||||
borderRadius: "$md",
|
borderRadius: "$md",
|
||||||
|
position: "relative",
|
||||||
|
mb: "15%",
|
||||||
boxShadow:
|
boxShadow:
|
||||||
"0px 10px 38px -5px rgba(22, 23, 24, 0.25), 0px 10px 20px -5px rgba(22, 23, 24, 0.2)",
|
"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",
|
width: "90vw",
|
||||||
maxWidth: "450px",
|
maxWidth: "450px",
|
||||||
maxHeight: "85vh",
|
// maxHeight: "85vh",
|
||||||
padding: 25,
|
padding: 25,
|
||||||
"@media (prefers-reduced-motion: no-preference)": {
|
"@media (prefers-reduced-motion: no-preference)": {
|
||||||
animation: `${contentShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
|
animation: `${contentShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
|
||||||
@@ -55,10 +60,9 @@ const StyledContent = styled(DialogPrimitive.Content, {
|
|||||||
|
|
||||||
const Content: React.FC<{ css?: Stiches.CSS }> = ({ css, children }) => {
|
const Content: React.FC<{ css?: Stiches.CSS }> = ({ css, children }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<StyledOverlay>
|
||||||
<StyledOverlay />
|
|
||||||
<StyledContent css={css}>{children}</StyledContent>
|
<StyledContent css={css}>{children}</StyledContent>
|
||||||
</div>
|
</StyledOverlay>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -83,3 +87,4 @@ export const DialogContent = Content;
|
|||||||
export const DialogTitle = StyledTitle;
|
export const DialogTitle = StyledTitle;
|
||||||
export const DialogDescription = StyledDescription;
|
export const DialogDescription = StyledDescription;
|
||||||
export const DialogClose = DialogPrimitive.Close;
|
export const DialogClose = DialogPrimitive.Close;
|
||||||
|
export const DialogPortal = DialogPrimitive.Portal;
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ const itemStyles = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
"&:focus": {
|
"&:focus": {
|
||||||
backgroundColor: "$pink9",
|
backgroundColor: "$purple9",
|
||||||
color: "$white",
|
color: "$white",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -85,8 +85,8 @@ const StyledRadioItem = styled(DropdownMenuPrimitive.RadioItem, {
|
|||||||
});
|
});
|
||||||
const StyledTriggerItem = styled(DropdownMenuPrimitive.TriggerItem, {
|
const StyledTriggerItem = styled(DropdownMenuPrimitive.TriggerItem, {
|
||||||
'&[data-state="open"]': {
|
'&[data-state="open"]': {
|
||||||
backgroundColor: "$pink9",
|
backgroundColor: "$purple9",
|
||||||
color: "$pink9",
|
color: "$purple9",
|
||||||
},
|
},
|
||||||
...itemStyles,
|
...itemStyles,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -25,8 +25,14 @@ import {
|
|||||||
import NewWindow from "react-new-window";
|
import NewWindow from "react-new-window";
|
||||||
import { signOut, useSession } from "next-auth/react";
|
import { signOut, useSession } from "next-auth/react";
|
||||||
import { useSnapshot } from "valtio";
|
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 state from "../state";
|
||||||
import Box from "./Box";
|
import Box from "./Box";
|
||||||
import Button from "./Button";
|
import Button from "./Button";
|
||||||
@@ -41,30 +47,20 @@ import {
|
|||||||
} from "./Dialog";
|
} from "./Dialog";
|
||||||
import Flex from "./Flex";
|
import Flex from "./Flex";
|
||||||
import Stack from "./Stack";
|
import Stack from "./Stack";
|
||||||
import Input from "./Input";
|
import { Input, Label } from "./Input";
|
||||||
import Text from "./Text";
|
import Text from "./Text";
|
||||||
import toast from "react-hot-toast";
|
import Tooltip from "./Tooltip";
|
||||||
import {
|
|
||||||
AlertDialog,
|
|
||||||
AlertDialogContent,
|
|
||||||
AlertDialogTitle,
|
|
||||||
AlertDialogDescription,
|
|
||||||
AlertDialogCancel,
|
|
||||||
AlertDialogAction,
|
|
||||||
} from "./AlertDialog";
|
|
||||||
import { styled } from "../stitches.config";
|
import { styled } from "../stitches.config";
|
||||||
|
import { showAlert } from "../state/actions/showAlert";
|
||||||
const DEFAULT_EXTENSION = ".c";
|
|
||||||
|
|
||||||
const ErrorText = styled(Text, {
|
const ErrorText = styled(Text, {
|
||||||
color: "$red9",
|
color: "$error",
|
||||||
mt: "$1",
|
mt: "$1",
|
||||||
display: "block",
|
display: "block",
|
||||||
});
|
});
|
||||||
|
|
||||||
const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
||||||
const snap = useSnapshot(state);
|
const snap = useSnapshot(state);
|
||||||
const [createNewAlertOpen, setCreateNewAlertOpen] = useState(false);
|
|
||||||
const [editorSettingsOpen, setEditorSettingsOpen] = useState(false);
|
const [editorSettingsOpen, setEditorSettingsOpen] = useState(false);
|
||||||
const [isNewfileDialogOpen, setIsNewfileDialogOpen] = useState(false);
|
const [isNewfileDialogOpen, setIsNewfileDialogOpen] = useState(false);
|
||||||
const [newfileError, setNewfileError] = useState<string | null>(null);
|
const [newfileError, setNewfileError] = useState<string | null>(null);
|
||||||
@@ -83,27 +79,57 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
|||||||
setNewfileError(null);
|
setNewfileError(null);
|
||||||
}, [filename, setNewfileError]);
|
}, [filename, setNewfileError]);
|
||||||
|
|
||||||
|
const showNewGistAlert = () => {
|
||||||
|
showAlert("Are you sure?", {
|
||||||
|
body: (
|
||||||
|
<>
|
||||||
|
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.
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
cancelText: "Cancel",
|
||||||
|
confirmText: "Create new Gist",
|
||||||
|
confirmPrefix: <FilePlus size="15px" />,
|
||||||
|
onConfirm: () => syncToGist(session, true),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const validateFilename = useCallback(
|
const validateFilename = useCallback(
|
||||||
(filename: string): { error: string | null } => {
|
(filename: string): { error: string | null } => {
|
||||||
|
// check if filename already exists
|
||||||
|
if (!filename) {
|
||||||
|
return { error: "You need to add filename" };
|
||||||
|
}
|
||||||
if (snap.files.find(file => file.name === filename)) {
|
if (snap.files.find(file => file.name === filename)) {
|
||||||
return { error: "Filename already exists." };
|
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 };
|
return { error: null };
|
||||||
},
|
},
|
||||||
[snap.files]
|
[snap.files]
|
||||||
);
|
);
|
||||||
const handleConfirm = useCallback(() => {
|
const handleConfirm = useCallback(() => {
|
||||||
// add default extension in case omitted
|
// add default extension in case omitted
|
||||||
let _filename = filename.includes(".") ? filename : filename + DEFAULT_EXTENSION;
|
const chk = validateFilename(filename);
|
||||||
const chk = validateFilename(_filename);
|
if (chk && chk.error) {
|
||||||
if (chk.error) {
|
|
||||||
setNewfileError(`Error: ${chk.error}`);
|
setNewfileError(`Error: ${chk.error}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsNewfileDialogOpen(false);
|
setIsNewfileDialogOpen(false);
|
||||||
createNewFile(_filename);
|
createNewFile(filename);
|
||||||
setFilename("");
|
setFilename("");
|
||||||
}, [filename, setIsNewfileDialogOpen, setFilename, validateFilename]);
|
}, [filename, setIsNewfileDialogOpen, setFilename, validateFilename]);
|
||||||
|
|
||||||
@@ -139,7 +165,9 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
|||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
outline={showWat ? snap.activeWat !== index : snap.active !== index}
|
outline={
|
||||||
|
showWat ? snap.activeWat !== index : snap.active !== index
|
||||||
|
}
|
||||||
onClick={() => (state.active = index)}
|
onClick={() => (state.active = index)}
|
||||||
key={file.name + index}
|
key={file.name + index}
|
||||||
css={{
|
css={{
|
||||||
@@ -174,7 +202,8 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
|||||||
// If deleted file is behind active tab
|
// If deleted file is behind active tab
|
||||||
// we keep the current state otherwise
|
// we keep the current state otherwise
|
||||||
// select previous file on the list
|
// 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" />
|
<X size="9px" weight="bold" />
|
||||||
@@ -184,16 +213,24 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{!showWat && (
|
{!showWat && (
|
||||||
<Dialog open={isNewfileDialogOpen} onOpenChange={setIsNewfileDialogOpen}>
|
<Dialog
|
||||||
|
open={isNewfileDialogOpen}
|
||||||
|
onOpenChange={setIsNewfileDialogOpen}
|
||||||
|
>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button ghost size="sm" css={{ alignItems: "center", px: "$2", mr: "$3" }}>
|
<Button
|
||||||
<Plus size="16px" /> {snap.files.length === 0 && "Add new file"}
|
ghost
|
||||||
|
size="sm"
|
||||||
|
css={{ alignItems: "center", px: "$2", mr: "$3" }}
|
||||||
|
>
|
||||||
|
<Plus size="16px" />{" "}
|
||||||
|
{snap.files.length === 0 && "Add new file"}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogTitle>Create new file</DialogTitle>
|
<DialogTitle>Create new file</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
<label>Filename</label>
|
<Label>Filename</Label>
|
||||||
<Input
|
<Input
|
||||||
value={filename}
|
value={filename}
|
||||||
onChange={e => setFilename(e.target.value)}
|
onChange={e => setFilename(e.target.value)}
|
||||||
@@ -216,10 +253,7 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
|||||||
<DialogClose asChild>
|
<DialogClose asChild>
|
||||||
<Button outline>Cancel</Button>
|
<Button outline>Cancel</Button>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
<Button
|
<Button variant="primary" onClick={handleConfirm}>
|
||||||
variant="primary"
|
|
||||||
onClick={handleConfirm}
|
|
||||||
>
|
|
||||||
Create file
|
Create file
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -237,11 +271,13 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
|||||||
<Flex
|
<Flex
|
||||||
css={{
|
css={{
|
||||||
py: "$3",
|
py: "$3",
|
||||||
backgroundColor: "$mauve3",
|
backgroundColor: "$mauve2",
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Container css={{ width: "unset", display: "flex", alignItems: "center" }}>
|
<Container
|
||||||
|
css={{ width: "unset", display: "flex", alignItems: "center" }}
|
||||||
|
>
|
||||||
{status === "authenticated" ? (
|
{status === "authenticated" ? (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
@@ -274,10 +310,15 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
|||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
<DropdownMenuItem disabled onClick={() => signOut()}>
|
<DropdownMenuItem disabled onClick={() => signOut()}>
|
||||||
<User size="16px" /> {session?.user?.username} ({session?.user.name})
|
<User size="16px" /> {session?.user?.username} (
|
||||||
|
{session?.user.name})
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<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" />
|
<ArrowSquareOut size="16px" />
|
||||||
Go to your Gist
|
Go to your Gist
|
||||||
@@ -291,7 +332,12 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
|||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</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
|
<GithubLogo size="16px" /> Login
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
@@ -330,36 +376,65 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button isLoading={snap.zipLoading} onClick={downloadAsZip} outline size="sm" css={{ alignItems: "center" }}>
|
<Tooltip content="Download as ZIP">
|
||||||
<DownloadSimple size="16px" />
|
<Button
|
||||||
</Button>
|
isLoading={snap.zipLoading}
|
||||||
<Button
|
onClick={downloadAsZip}
|
||||||
outline
|
outline
|
||||||
size="sm"
|
size="sm"
|
||||||
css={{ alignItems: "center" }}
|
css={{ alignItems: "center" }}
|
||||||
onClick={() => {
|
>
|
||||||
navigator.clipboard.writeText(`${window.location.origin}/develop/${snap.gistId}`);
|
<DownloadSimple size="16px" />
|
||||||
toast.success("Copied share link to clipboard!");
|
</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
|
||||||
<Button
|
size="sm"
|
||||||
outline
|
isDisabled={!session || !session.user}
|
||||||
size="sm"
|
isLoading={snap.gistLoading}
|
||||||
disabled={!session || !session.user}
|
css={{ alignItems: "center" }}
|
||||||
isLoading={snap.gistLoading}
|
onClick={() => {
|
||||||
css={{ alignItems: "center" }}
|
if (!session || !session.user) {
|
||||||
onClick={() => {
|
return;
|
||||||
if (snap.gistOwner === session?.user.username) {
|
}
|
||||||
syncToGist(session);
|
if (snap.gistOwner === session?.user.username) {
|
||||||
} else {
|
syncToGist(session);
|
||||||
setCreateNewAlertOpen(true);
|
} else {
|
||||||
}
|
showNewGistAlert();
|
||||||
}}
|
}
|
||||||
>
|
}}
|
||||||
<CloudArrowUp size="16px" />
|
>
|
||||||
</Button>
|
{snap.gistOwner === session?.user.username ? (
|
||||||
|
<CloudArrowUp size="16px" />
|
||||||
|
) : (
|
||||||
|
<FilePlus size="16px" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
@@ -368,7 +443,10 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
<DropdownMenuItem disabled={snap.zipLoading} onClick={downloadAsZip}>
|
<DropdownMenuItem
|
||||||
|
disabled={snap.zipLoading}
|
||||||
|
onClick={downloadAsZip}
|
||||||
|
>
|
||||||
<DownloadSimple size="16px" /> Download as ZIP
|
<DownloadSimple size="16px" /> Download as ZIP
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
@@ -383,7 +461,9 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
|||||||
Copy share link to clipboard
|
Copy share link to clipboard
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
disabled={session?.user.username !== snap.gistOwner || !snap.gistId}
|
disabled={
|
||||||
|
session?.user.username !== snap.gistOwner || !snap.gistId
|
||||||
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
syncToGist(session);
|
syncToGist(session);
|
||||||
}}
|
}}
|
||||||
@@ -394,7 +474,7 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
|||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
disabled={status !== "authenticated"}
|
disabled={status !== "authenticated"}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCreateNewAlertOpen(true);
|
showNewGistAlert();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FilePlus size="16px" /> Create as a new Gist
|
<FilePlus size="16px" /> Create as a new Gist
|
||||||
@@ -409,33 +489,11 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
|||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
{popup && !session ? <NewWindow center="parent" url="/sign-in" /> : null}
|
{popup && !session ? (
|
||||||
|
<NewWindow center="parent" url="/sign-in" />
|
||||||
|
) : null}
|
||||||
</Container>
|
</Container>
|
||||||
</Flex>
|
</Flex>
|
||||||
<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.
|
|
||||||
</AlertDialogDescription>
|
|
||||||
<Flex css={{ justifyContent: "flex-end", gap: "$3" }}>
|
|
||||||
<AlertDialogCancel asChild>
|
|
||||||
<Button outline>Cancel</Button>
|
|
||||||
</AlertDialogCancel>
|
|
||||||
<AlertDialogAction asChild>
|
|
||||||
<Button
|
|
||||||
variant="primary"
|
|
||||||
onClick={() => {
|
|
||||||
syncToGist(session, true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FilePlus size="15px" /> Create new Gist
|
|
||||||
</Button>
|
|
||||||
</AlertDialogAction>
|
|
||||||
</Flex>
|
|
||||||
</AlertDialogContent>
|
|
||||||
</AlertDialog>
|
|
||||||
|
|
||||||
<Dialog open={editorSettingsOpen} onOpenChange={setEditorSettingsOpen}>
|
<Dialog open={editorSettingsOpen} onOpenChange={setEditorSettingsOpen}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
@@ -446,7 +504,7 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
|||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogTitle>Editor settings</DialogTitle>
|
<DialogTitle>Editor settings</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
<label>Tab size</label>
|
<Label>Tab size</Label>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
@@ -462,12 +520,18 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
|||||||
|
|
||||||
<Flex css={{ marginTop: 25, justifyContent: "flex-end", gap: "$3" }}>
|
<Flex css={{ marginTop: 25, justifyContent: "flex-end", gap: "$3" }}>
|
||||||
<DialogClose asChild>
|
<DialogClose asChild>
|
||||||
<Button outline onClick={() => updateEditorSettings(editorSettings)}>
|
<Button
|
||||||
|
outline
|
||||||
|
onClick={() => updateEditorSettings(editorSettings)}
|
||||||
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
<DialogClose asChild>
|
<DialogClose asChild>
|
||||||
<Button variant="primary" onClick={() => updateEditorSettings(editorSettings)}>
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
onClick={() => updateEditorSettings(editorSettings)}
|
||||||
|
>
|
||||||
Save changes
|
Save changes
|
||||||
</Button>
|
</Button>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
|
|||||||
@@ -3,6 +3,51 @@ import Box from "./Box";
|
|||||||
|
|
||||||
const Flex = styled(Box, {
|
const Flex = styled(Box, {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
variants: {
|
||||||
|
row: {
|
||||||
|
true: {
|
||||||
|
flexDirection: "row",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
true: {
|
||||||
|
flexDirection: "column",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fluid: {
|
||||||
|
true: {
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default Flex;
|
export default Flex;
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import { createLanguageClient, createWebSocket } from "../utils/languageClient";
|
|||||||
import { listen } from "@codingame/monaco-jsonrpc";
|
import { listen } from "@codingame/monaco-jsonrpc";
|
||||||
import ReconnectingWebSocket from "reconnecting-websocket";
|
import ReconnectingWebSocket from "reconnecting-websocket";
|
||||||
|
|
||||||
|
import docs from "../xrpl-hooks-docs/docs";
|
||||||
|
|
||||||
loader.config({
|
loader.config({
|
||||||
paths: {
|
paths: {
|
||||||
vs: "https://cdn.jsdelivr.net/npm/monaco-editor@0.30.1/min/vs",
|
vs: "https://cdn.jsdelivr.net/npm/monaco-editor@0.30.1/min/vs",
|
||||||
@@ -29,15 +31,75 @@ loader.config({
|
|||||||
|
|
||||||
const validateWritability = (editor: monaco.editor.IStandaloneCodeEditor) => {
|
const validateWritability = (editor: monaco.editor.IStandaloneCodeEditor) => {
|
||||||
const currPath = editor.getModel()?.uri.path;
|
const currPath = editor.getModel()?.uri.path;
|
||||||
if (apiHeaderFiles.find(h => currPath?.endsWith(h))) {
|
if (apiHeaderFiles.find((h) => currPath?.endsWith(h))) {
|
||||||
editor.updateOptions({ readOnly: true });
|
editor.updateOptions({ readOnly: true });
|
||||||
} else {
|
} else {
|
||||||
editor.updateOptions({ readOnly: false });
|
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 = 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-")
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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 HooksEditor = () => {
|
||||||
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>();
|
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>();
|
||||||
|
const monacoRef = useRef<typeof monaco>();
|
||||||
const subscriptionRef = useRef<ReconnectingWebSocket | null>(null);
|
const subscriptionRef = useRef<ReconnectingWebSocket | null>(null);
|
||||||
const snap = useSnapshot(state);
|
const snap = useSnapshot(state);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -52,6 +114,11 @@ const HooksEditor = () => {
|
|||||||
subscriptionRef?.current?.close();
|
subscriptionRef?.current?.close();
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
useEffect(() => {
|
||||||
|
if (monacoRef.current) {
|
||||||
|
setMarkers(monacoRef.current);
|
||||||
|
}
|
||||||
|
}, [snap.active]);
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
css={{
|
css={{
|
||||||
@@ -60,7 +127,7 @@ const HooksEditor = () => {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
backgroundColor: "$mauve3",
|
backgroundColor: "$mauve2",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -71,20 +138,19 @@ const HooksEditor = () => {
|
|||||||
keepCurrentModel
|
keepCurrentModel
|
||||||
defaultLanguage={snap.files?.[snap.active]?.language}
|
defaultLanguage={snap.files?.[snap.active]?.language}
|
||||||
language={snap.files?.[snap.active]?.language}
|
language={snap.files?.[snap.active]?.language}
|
||||||
path={`file://tmp/c/${snap.files?.[snap.active]?.name}`}
|
path={`file:///work/c/${snap.files?.[snap.active]?.name}`}
|
||||||
defaultValue={snap.files?.[snap.active]?.content}
|
defaultValue={snap.files?.[snap.active]?.content}
|
||||||
beforeMount={monaco => {
|
beforeMount={(monaco) => {
|
||||||
if (!snap.editorCtx) {
|
if (!snap.editorCtx) {
|
||||||
snap.files.forEach(file =>
|
snap.files.forEach((file) =>
|
||||||
monaco.editor.createModel(
|
monaco.editor.createModel(
|
||||||
file.content,
|
file.content,
|
||||||
file.language,
|
file.language,
|
||||||
monaco.Uri.parse(`file://tmp/c/${file.name}`)
|
monaco.Uri.parse(`file:///work/c/${file.name}`)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// monaco.editor.createModel(value, 'c', monaco.Uri.parse('file:///tmp/c/file.c'))
|
|
||||||
// create the web socket
|
// create the web socket
|
||||||
if (!subscriptionRef.current) {
|
if (!subscriptionRef.current) {
|
||||||
monaco.languages.register({
|
monaco.languages.register({
|
||||||
@@ -101,7 +167,7 @@ const HooksEditor = () => {
|
|||||||
// listen when the web socket is opened
|
// listen when the web socket is opened
|
||||||
listen({
|
listen({
|
||||||
webSocket: webSocket as WebSocket,
|
webSocket: webSocket as WebSocket,
|
||||||
onConnection: connection => {
|
onConnection: (connection) => {
|
||||||
// create and start the language client
|
// create and start the language client
|
||||||
const languageClient = createLanguageClient(connection);
|
const languageClient = createLanguageClient(connection);
|
||||||
const disposable = languageClient.start();
|
const disposable = languageClient.start();
|
||||||
@@ -134,16 +200,45 @@ const HooksEditor = () => {
|
|||||||
}}
|
}}
|
||||||
onMount={(editor, monaco) => {
|
onMount={(editor, monaco) => {
|
||||||
editorRef.current = editor;
|
editorRef.current = editor;
|
||||||
|
monacoRef.current = monaco;
|
||||||
editor.updateOptions({
|
editor.updateOptions({
|
||||||
glyphMargin: true,
|
glyphMargin: true,
|
||||||
lightbulb: {
|
lightbulb: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
|
editor.addCommand(
|
||||||
saveFile();
|
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"}
|
theme={theme === "dark" ? "dark" : "light"}
|
||||||
/>
|
/>
|
||||||
@@ -161,7 +256,9 @@ const HooksEditor = () => {
|
|||||||
<Box css={{ display: "inline-flex", pl: "35px" }}>
|
<Box css={{ display: "inline-flex", pl: "35px" }}>
|
||||||
<ArrowBendLeftUp size={30} />
|
<ArrowBendLeftUp size={30} />
|
||||||
</Box>
|
</Box>
|
||||||
<Box css={{ pl: "0px", pt: "15px", flex: 1, display: "inline-flex" }}>
|
<Box
|
||||||
|
css={{ pl: "0px", pt: "15px", flex: 1, display: "inline-flex" }}
|
||||||
|
>
|
||||||
<Text
|
<Text
|
||||||
css={{
|
css={{
|
||||||
fontSize: "14px",
|
fontSize: "14px",
|
||||||
@@ -169,7 +266,7 @@ const HooksEditor = () => {
|
|||||||
fontFamily: "$monospace",
|
fontFamily: "$monospace",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Click the link above to create a your file
|
Click the link above to create your file
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
import React from "react";
|
||||||
import { styled } from "../stitches.config";
|
import { styled } from "../stitches.config";
|
||||||
|
import * as LabelPrim from '@radix-ui/react-label';
|
||||||
|
|
||||||
export const Input = styled("input", {
|
export const Input = styled("input", {
|
||||||
// Reset
|
// Reset
|
||||||
@@ -58,6 +60,8 @@ export const Input = styled("input", {
|
|||||||
},
|
},
|
||||||
"&:read-only": {
|
"&:read-only": {
|
||||||
backgroundColor: "$mauve2",
|
backgroundColor: "$mauve2",
|
||||||
|
color: "$text",
|
||||||
|
opacity: 0.8,
|
||||||
"&:focus": {
|
"&:focus": {
|
||||||
boxShadow: "inset 0px 0px 0px 1px $colors$mauve7",
|
boxShadow: "inset 0px 0px 0px 1px $colors$mauve7",
|
||||||
},
|
},
|
||||||
@@ -110,20 +114,24 @@ export const Input = styled("input", {
|
|||||||
backgroundColor: "transparent",
|
backgroundColor: "transparent",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
deep: {
|
||||||
|
backgroundColor: "$deep",
|
||||||
|
boxShadow: "none",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
invalid: {
|
invalid: {
|
||||||
boxShadow: "inset 0 0 0 1px $colors$red7",
|
boxShadow: "inset 0 0 0 1px $colors$crimson7",
|
||||||
"&:focus": {
|
"&:focus": {
|
||||||
boxShadow:
|
boxShadow:
|
||||||
"inset 0px 0px 0px 1px $colors$red8, 0px 0px 0px 1px $colors$red8",
|
"inset 0px 0px 0px 1px $colors$crimson8, 0px 0px 0px 1px $colors$crimson8",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
valid: {
|
valid: {
|
||||||
boxShadow: "inset 0 0 0 1px $colors$green7",
|
boxShadow: "inset 0 0 0 1px $colors$grass7",
|
||||||
"&:focus": {
|
"&:focus": {
|
||||||
boxShadow:
|
boxShadow:
|
||||||
"inset 0px 0px 0px 1px $colors$green8, 0px 0px 0px 1px $colors$green8",
|
"inset 0px 0px 0px 1px $colors$grass8, 0px 0px 0px 1px $colors$grass8",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -144,4 +152,18 @@ 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;
|
||||||
|
|
||||||
|
|
||||||
|
const LabelRoot = (props: LabelPrim.LabelProps) => <LabelPrim.Root {...props} />
|
||||||
|
|
||||||
|
export const Label = styled(LabelRoot, {
|
||||||
|
display: 'inline-block',
|
||||||
|
mb: '$1'
|
||||||
|
})
|
||||||
@@ -3,6 +3,14 @@ import { styled } from "../stitches.config";
|
|||||||
const StyledLink = styled("a", {
|
const StyledLink = styled("a", {
|
||||||
color: "CurrentColor",
|
color: "CurrentColor",
|
||||||
textDecoration: "underline",
|
textDecoration: "underline",
|
||||||
|
cursor: 'pointer',
|
||||||
|
variants: {
|
||||||
|
highlighted: {
|
||||||
|
true: {
|
||||||
|
color: '$blue9'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default StyledLink;
|
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 { Notepad, Prohibit } from "phosphor-react";
|
||||||
import useStayScrolled from "react-stay-scrolled";
|
import useStayScrolled from "react-stay-scrolled";
|
||||||
import NextLink from "next/link";
|
import NextLink from "next/link";
|
||||||
|
|
||||||
import Container from "./Container";
|
import Container from "./Container";
|
||||||
import Box from "./Box";
|
|
||||||
import Flex from "./Flex";
|
|
||||||
import LogText from "./LogText";
|
import LogText from "./LogText";
|
||||||
import { ILog } from "../state";
|
import state, { ILog } from "../state";
|
||||||
import Text from "./Text";
|
import { Pre, Link, Heading, Button, Text, Flex, Box } from ".";
|
||||||
import Button from "./Button";
|
import regexifyString from "regexify-string";
|
||||||
import Heading from "./Heading";
|
import { useSnapshot } from "valtio";
|
||||||
import Link from "./Link";
|
import { AccountDialog } from "./Accounts";
|
||||||
|
|
||||||
interface ILogBox {
|
interface ILogBox {
|
||||||
title: string;
|
title: string;
|
||||||
clearLog?: () => void;
|
clearLog?: () => void;
|
||||||
logs: ILog[];
|
logs: ILog[];
|
||||||
|
renderNav?: () => ReactNode;
|
||||||
|
enhanced?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children }) => {
|
const LogBox: FC<ILogBox> = ({
|
||||||
|
title,
|
||||||
|
clearLog,
|
||||||
|
logs,
|
||||||
|
children,
|
||||||
|
renderNav,
|
||||||
|
enhanced,
|
||||||
|
}) => {
|
||||||
const logRef = useRef<HTMLPreElement>(null);
|
const logRef = useRef<HTMLPreElement>(null);
|
||||||
const { stayScrolled /*, scrollBottom*/ } = useStayScrolled(logRef);
|
const { stayScrolled /*, scrollBottom*/ } = useStayScrolled(logRef);
|
||||||
|
|
||||||
@@ -36,10 +50,24 @@ const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children }) => {
|
|||||||
background: "$mauve1",
|
background: "$mauve1",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
height: "100%",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Container css={{ px: 0, flexShrink: 1 }}>
|
<Container
|
||||||
<Flex css={{ py: "$3" }}>
|
css={{
|
||||||
|
px: 0,
|
||||||
|
height: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
fluid
|
||||||
|
css={{
|
||||||
|
height: "48px",
|
||||||
|
alignItems: "center",
|
||||||
|
fontSize: "$sm",
|
||||||
|
fontWeight: 300,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Heading
|
<Heading
|
||||||
as="h3"
|
as="h3"
|
||||||
css={{
|
css={{
|
||||||
@@ -56,6 +84,15 @@ const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children }) => {
|
|||||||
>
|
>
|
||||||
<Notepad size="15px" /> <Text css={{ lineHeight: 1 }}>{title}</Text>
|
<Notepad size="15px" /> <Text css={{ lineHeight: 1 }}>{title}</Text>
|
||||||
</Heading>
|
</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" }}>
|
<Flex css={{ ml: "auto", gap: "$3", marginRight: "$3" }}>
|
||||||
{clearLog && (
|
{clearLog && (
|
||||||
<Button ghost size="xs" onClick={clearLog}>
|
<Button ghost size="xs" onClick={clearLog}>
|
||||||
@@ -64,6 +101,7 @@ const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children }) => {
|
|||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
as="pre"
|
as="pre"
|
||||||
ref={logRef}
|
ref={logRef}
|
||||||
@@ -73,29 +111,31 @@ const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children }) => {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "160px",
|
height: "calc(100% - 48px)", // 100% minus the logbox header height
|
||||||
|
overflowY: "auto",
|
||||||
fontSize: "13px",
|
fontSize: "13px",
|
||||||
fontWeight: "$body",
|
fontWeight: "$body",
|
||||||
fontFamily: "$monospace",
|
fontFamily: "$monospace",
|
||||||
px: "$3",
|
px: "$3",
|
||||||
pb: "$2",
|
pb: "$2",
|
||||||
whiteSpace: "normal",
|
whiteSpace: "normal",
|
||||||
overflowY: "auto",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{logs?.map((log, index) => (
|
{logs?.map((log, index) => (
|
||||||
<Box as="span" key={log.type + index}>
|
<Box
|
||||||
{/* <LogText capitalize variant={log.type}>
|
as="span"
|
||||||
{log.type}:{" "}
|
key={log.type + index}
|
||||||
</LogText> */}
|
css={{
|
||||||
<LogText variant={log.type}>
|
"@hover": {
|
||||||
{log.message}{" "}
|
"&:hover": {
|
||||||
{log.link && (
|
backgroundColor: enhanced ? "$backgroundAlt" : undefined,
|
||||||
<NextLink href={log.link} shallow passHref>
|
},
|
||||||
<Link as="a">{log.linkText}</Link>
|
},
|
||||||
</NextLink>
|
p: enhanced ? "$1" : undefined,
|
||||||
)}
|
my: enhanced ? "$1" : undefined,
|
||||||
</LogText>
|
}}
|
||||||
|
>
|
||||||
|
<Log {...log} />
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
{children}
|
{children}
|
||||||
@@ -105,4 +145,88 @@ const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const Log: FC<ILog> = ({
|
||||||
|
type,
|
||||||
|
timestring,
|
||||||
|
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]
|
||||||
|
);
|
||||||
|
|
||||||
|
let message: ReactNode;
|
||||||
|
|
||||||
|
if (typeof _message === 'string') {
|
||||||
|
_message = _message.trim().replace(/\n /gi, "\n");
|
||||||
|
message = enrichAccounts(_message)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
message = _message
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsonData = enrichAccounts(_jsonData);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AccountDialog
|
||||||
|
setActiveAccountAddress={setDialogAccount}
|
||||||
|
activeAccountAddress={dialogAccount}
|
||||||
|
/>
|
||||||
|
<LogText variant={type}>
|
||||||
|
{timestring && (
|
||||||
|
<Text muted monospace>
|
||||||
|
{timestring}{" "}
|
||||||
|
</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;
|
export default LogBox;
|
||||||
|
|||||||
@@ -4,19 +4,20 @@ const Text = styled("span", {
|
|||||||
fontFamily: "$monospace",
|
fontFamily: "$monospace",
|
||||||
lineHeight: "$body",
|
lineHeight: "$body",
|
||||||
color: "$text",
|
color: "$text",
|
||||||
|
wordWrap: "break-word",
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
log: {
|
log: {
|
||||||
color: "$text",
|
color: "$text",
|
||||||
},
|
},
|
||||||
warning: {
|
warning: {
|
||||||
color: "$yellow11",
|
color: "$warning",
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
color: "$red11",
|
color: "$error",
|
||||||
},
|
},
|
||||||
success: {
|
success: {
|
||||||
color: "$green11",
|
color: "$success",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
capitalize: {
|
capitalize: {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { styled } from "../stitches.config";
|
import { styled } from "../stitches.config";
|
||||||
|
|
||||||
const SVG = styled("svg", {
|
const SVG = styled("svg", {
|
||||||
"& #path-one, & #path-two": {
|
"& #path": {
|
||||||
fill: "$text",
|
fill: "$accent",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
function Logo({
|
function Logo({
|
||||||
@@ -14,21 +14,18 @@ function Logo({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<SVG
|
<SVG
|
||||||
width={width || "1em"}
|
width={width || "1.1em"}
|
||||||
height={height || "1em"}
|
height={height || "1.1em"}
|
||||||
viewBox="0 0 28 22"
|
viewBox="0 0 294 283"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
fillRule="evenodd"
|
||||||
clipRule="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>
|
</SVG>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -27,7 +27,29 @@ import {
|
|||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "./Dialog";
|
} from "./Dialog";
|
||||||
import PanelBox from "./PanelBox";
|
import PanelBox from "./PanelBox";
|
||||||
import { templateFileIds } from '../state/constants';
|
import { templateFileIds } from "../state/constants";
|
||||||
|
import { styled } from "../stitches.config";
|
||||||
|
|
||||||
|
import Starter from "../components/icons/Starter";
|
||||||
|
import Firewall from "../components/icons/Firewall";
|
||||||
|
import Notary from "../components/icons/Notary";
|
||||||
|
import Carbon from "../components/icons/Carbon";
|
||||||
|
import Peggy from "../components/icons/Peggy";
|
||||||
|
|
||||||
|
const ImageWrapper = styled(Flex, {
|
||||||
|
position: "relative",
|
||||||
|
mt: "$2",
|
||||||
|
mb: "$10",
|
||||||
|
svg: {
|
||||||
|
// fill: "red",
|
||||||
|
".angle": {
|
||||||
|
fill: "$text",
|
||||||
|
},
|
||||||
|
":not(.angle)": {
|
||||||
|
stroke: "$text",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const Navigation = () => {
|
const Navigation = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -40,9 +62,11 @@ const Navigation = () => {
|
|||||||
as="nav"
|
as="nav"
|
||||||
css={{
|
css={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
backgroundColor: "$mauve1",
|
||||||
borderBottom: "1px solid $mauve6",
|
borderBottom: "1px solid $mauve6",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
zIndex: 2003,
|
zIndex: 2003,
|
||||||
|
height: "60px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Container
|
<Container
|
||||||
@@ -60,7 +84,7 @@ const Navigation = () => {
|
|||||||
pr: "$4",
|
pr: "$4",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Link href="/" passHref>
|
<Link href={gistId ? `/develop/${gistId}` : "/develop"} passHref>
|
||||||
<Box
|
<Box
|
||||||
as="a"
|
as="a"
|
||||||
css={{
|
css={{
|
||||||
@@ -69,7 +93,7 @@ const Navigation = () => {
|
|||||||
color: "$textColor",
|
color: "$textColor",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Logo width="30px" height="30px" />
|
<Logo width="32px" height="32px" />
|
||||||
</Box>
|
</Box>
|
||||||
</Link>
|
</Link>
|
||||||
<Flex
|
<Flex
|
||||||
@@ -83,20 +107,42 @@ const Navigation = () => {
|
|||||||
<Spinner />
|
<Spinner />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Heading css={{ lineHeight: 1 }}>{snap.files?.[0]?.name || "XRPL Hooks"}</Heading>
|
<Heading css={{ lineHeight: 1 }}>
|
||||||
<Text css={{ fontSize: "$xs", color: "$mauve10", lineHeight: 1 }}>
|
{snap.files?.[0]?.name || "XRPL Hooks"}
|
||||||
{snap.files.length > 0 ? "Gist: " : "Playground"}
|
</Heading>
|
||||||
<Text css={{ color: "$mauve12" }}>
|
<Text
|
||||||
{snap.files.length > 0 &&
|
css={{ fontSize: "$xs", color: "$mauve10", lineHeight: 1 }}
|
||||||
`${snap.gistOwner || "-"}/${truncate(snap.gistId || "")}`}
|
>
|
||||||
</Text>
|
{snap.files.length > 0 ? "Gist: " : "Builder"}
|
||||||
|
{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>
|
</Text>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
{router.isReady && (
|
{router.isReady && (
|
||||||
<ButtonGroup css={{ marginLeft: "auto" }}>
|
<ButtonGroup css={{ marginLeft: "auto" }}>
|
||||||
<Dialog open={snap.mainModalOpen} onOpenChange={open => (state.mainModalOpen = open)}>
|
<Dialog
|
||||||
|
open={snap.mainModalOpen}
|
||||||
|
onOpenChange={(open) => (state.mainModalOpen = open)}
|
||||||
|
>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button outline>
|
<Button outline>
|
||||||
<FolderOpen size="15px" />
|
<FolderOpen size="15px" />
|
||||||
@@ -104,19 +150,20 @@ const Navigation = () => {
|
|||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
css={{
|
css={{
|
||||||
maxWidth: "100%",
|
display: "flex",
|
||||||
|
maxWidth: "1080px",
|
||||||
width: "80vw",
|
width: "80vw",
|
||||||
height: "80%",
|
maxHeight: "80%",
|
||||||
backgroundColor: "$mauve1 !important",
|
backgroundColor: "$mauve1 !important",
|
||||||
overflowY: "auto",
|
overflowY: "auto",
|
||||||
|
background: "black",
|
||||||
p: 0,
|
p: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
css={{
|
css={{
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
flex: 1,
|
height: "100%",
|
||||||
height: "auto",
|
|
||||||
"@md": {
|
"@md": {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
@@ -127,11 +174,13 @@ const Navigation = () => {
|
|||||||
css={{
|
css={{
|
||||||
borderBottom: "1px solid $colors$mauve5",
|
borderBottom: "1px solid $colors$mauve5",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
|
minWidth: "240px",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
p: "$7",
|
p: "$7",
|
||||||
height: "100%",
|
backgroundColor: "$mauve2",
|
||||||
"@md": {
|
"@md": {
|
||||||
width: "30%",
|
width: "30%",
|
||||||
|
maxWidth: "300px",
|
||||||
borderBottom: "0px",
|
borderBottom: "0px",
|
||||||
borderRight: "1px solid $colors$mauve5",
|
borderRight: "1px solid $colors$mauve5",
|
||||||
},
|
},
|
||||||
@@ -144,9 +193,11 @@ const Navigation = () => {
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: "$3",
|
gap: "$3",
|
||||||
fontSize: "$xl",
|
fontSize: "$xl",
|
||||||
|
lineHeight: "$one",
|
||||||
|
fontWeight: "$bold",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Logo width="30px" height="30px" /> XRPL Hooks Editor
|
<Logo width="48px" height="48px" /> XRPL Hooks Builder
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription as="div">
|
<DialogDescription as="div">
|
||||||
<Text
|
<Text
|
||||||
@@ -157,17 +208,20 @@ const Navigation = () => {
|
|||||||
mb: "$7",
|
mb: "$7",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Hooks add smart contract functionality to the XRP Ledger.
|
Hooks add smart contract functionality to the XRP
|
||||||
|
Ledger.
|
||||||
</Text>
|
</Text>
|
||||||
<Flex css={{ flexDirection: "column", gap: "$2", mt: "$2" }}>
|
<Flex
|
||||||
|
css={{ flexDirection: "column", gap: "$2", mt: "$2" }}
|
||||||
|
>
|
||||||
<Text
|
<Text
|
||||||
css={{
|
css={{
|
||||||
display: "inline-flex",
|
display: "inline-flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: "$3",
|
gap: "$3",
|
||||||
color: "$green9",
|
color: "$purple11",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
color: "$green11 !important",
|
color: "$purple12",
|
||||||
},
|
},
|
||||||
"&:focus": {
|
"&:focus": {
|
||||||
outline: 0,
|
outline: 0,
|
||||||
@@ -178,7 +232,7 @@ const Navigation = () => {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://github.com/XRPL-Labs/xrpld-hooks"
|
href="https://github.com/XRPL-Labs/xrpld-hooks"
|
||||||
>
|
>
|
||||||
<ArrowUpRight size="15px" /> Developing Hooks
|
<ArrowUpRight size="15px" /> Hooks Github
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text
|
<Text
|
||||||
@@ -186,9 +240,9 @@ const Navigation = () => {
|
|||||||
display: "inline-flex",
|
display: "inline-flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: "$3",
|
gap: "$3",
|
||||||
color: "$green9",
|
color: "$purple11",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
color: "$green11 !important",
|
color: "$purple12",
|
||||||
},
|
},
|
||||||
"&:focus": {
|
"&:focus": {
|
||||||
outline: 0,
|
outline: 0,
|
||||||
@@ -197,7 +251,7 @@ const Navigation = () => {
|
|||||||
as="a"
|
as="a"
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://xrpl-hooks.readme.io/docs"
|
href="https://xrpl-hooks.readme.io/v2.0/docs"
|
||||||
>
|
>
|
||||||
<ArrowUpRight size="15px" /> Hooks documentation
|
<ArrowUpRight size="15px" /> Hooks documentation
|
||||||
</Text>
|
</Text>
|
||||||
@@ -206,9 +260,9 @@ const Navigation = () => {
|
|||||||
display: "inline-flex",
|
display: "inline-flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: "$3",
|
gap: "$3",
|
||||||
color: "$green9",
|
color: "$purple11",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
color: "$green11 !important",
|
color: "$purple12",
|
||||||
},
|
},
|
||||||
"&:focus": {
|
"&:focus": {
|
||||||
outline: 0,
|
outline: 0,
|
||||||
@@ -232,41 +286,80 @@ const Navigation = () => {
|
|||||||
gridTemplateRows: "max-content",
|
gridTemplateRows: "max-content",
|
||||||
flex: 1,
|
flex: 1,
|
||||||
p: "$7",
|
p: "$7",
|
||||||
|
pb: "$16",
|
||||||
gap: "$3",
|
gap: "$3",
|
||||||
alignItems: "flex-start",
|
alignItems: "normal",
|
||||||
flexWrap: "wrap",
|
flexWrap: "wrap",
|
||||||
backgroundImage: `url('/pattern.svg'), url('/pattern-2.svg')`,
|
backgroundColor: "$mauve1",
|
||||||
backgroundRepeat: "no-repeat",
|
|
||||||
backgroundPosition: "bottom left, top right",
|
|
||||||
"@md": {
|
"@md": {
|
||||||
|
gridTemplateColumns: "1fr 1fr",
|
||||||
|
gridTemplateRows: "max-content",
|
||||||
|
},
|
||||||
|
"@lg": {
|
||||||
gridTemplateColumns: "1fr 1fr 1fr",
|
gridTemplateColumns: "1fr 1fr 1fr",
|
||||||
gridTemplateRows: "max-content",
|
gridTemplateRows: "max-content",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PanelBox as="a" href={`/develop/${templateFileIds.starter}`}>
|
<PanelBox
|
||||||
|
as="a"
|
||||||
|
href={`/develop/${templateFileIds.starter}`}
|
||||||
|
>
|
||||||
|
<ImageWrapper>
|
||||||
|
<Starter />
|
||||||
|
</ImageWrapper>
|
||||||
<Heading>Starter</Heading>
|
<Heading>Starter</Heading>
|
||||||
<Text>Just an empty starter with essential imports</Text>
|
|
||||||
|
<Text>
|
||||||
|
Just a basic starter with essential imports, just
|
||||||
|
accepts any transaction coming through
|
||||||
|
</Text>
|
||||||
</PanelBox>
|
</PanelBox>
|
||||||
<PanelBox as="a" href={`/develop/${templateFileIds.starter}`}>
|
|
||||||
|
<PanelBox
|
||||||
|
as="a"
|
||||||
|
href={`/develop/${templateFileIds.firewall}`}
|
||||||
|
css={{ alignItems: "flex-start" }}
|
||||||
|
>
|
||||||
|
<ImageWrapper>
|
||||||
|
<Firewall />
|
||||||
|
</ImageWrapper>
|
||||||
<Heading>Firewall</Heading>
|
<Heading>Firewall</Heading>
|
||||||
<Text>This Hook essentially checks a blacklist of accounts</Text>
|
<Text>
|
||||||
|
This Hook essentially checks a blacklist of accounts
|
||||||
|
</Text>
|
||||||
</PanelBox>
|
</PanelBox>
|
||||||
<PanelBox as="a" href={`/develop/${templateFileIds.accept}`}>
|
<PanelBox
|
||||||
<Heading>Accept</Heading>
|
as="a"
|
||||||
<Text>This hook just accepts any transaction coming through it</Text>
|
href={`/develop/${templateFileIds.notary}`}
|
||||||
</PanelBox>
|
>
|
||||||
<PanelBox as="a" href={`/develop/${templateFileIds.notary}`}>
|
<ImageWrapper>
|
||||||
|
<Notary />
|
||||||
|
</ImageWrapper>
|
||||||
<Heading>Notary</Heading>
|
<Heading>Notary</Heading>
|
||||||
<Text>Collecting signatures for multi-sign transactions</Text>
|
<Text>
|
||||||
|
Collecting signatures for multi-sign transactions
|
||||||
|
</Text>
|
||||||
</PanelBox>
|
</PanelBox>
|
||||||
<PanelBox as="a" href={`/develop/${templateFileIds.carbon}`}>
|
<PanelBox
|
||||||
|
as="a"
|
||||||
|
href={`/develop/${templateFileIds.carbon}`}
|
||||||
|
>
|
||||||
|
<ImageWrapper>
|
||||||
|
<Carbon />
|
||||||
|
</ImageWrapper>
|
||||||
<Heading>Carbon</Heading>
|
<Heading>Carbon</Heading>
|
||||||
<Text>Send a percentage of sum to an address</Text>
|
<Text>Send a percentage of sum to an address</Text>
|
||||||
</PanelBox>
|
</PanelBox>
|
||||||
<PanelBox as="a" href={`/develop/${templateFileIds.peggy}`}>
|
<PanelBox
|
||||||
|
as="a"
|
||||||
|
href={`/develop/${templateFileIds.peggy}`}
|
||||||
|
>
|
||||||
|
<ImageWrapper>
|
||||||
|
<Peggy />
|
||||||
|
</ImageWrapper>
|
||||||
<Heading>Peggy</Heading>
|
<Heading>Peggy</Heading>
|
||||||
<Text>An oracle based stabe coin hook</Text>
|
<Text>An oracle based stable coin hook</Text>
|
||||||
</PanelBox>
|
</PanelBox>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -313,25 +406,53 @@ const Navigation = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<Link href={gistId ? `/develop/${gistId}` : "/develop"} passHref shallow>
|
<Link
|
||||||
<Button as="a" outline={!router.pathname.includes("/develop")} uppercase>
|
href={gistId ? `/develop/${gistId}` : "/develop"}
|
||||||
|
passHref
|
||||||
|
shallow
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
as="a"
|
||||||
|
outline={!router.pathname.includes("/develop")}
|
||||||
|
uppercase
|
||||||
|
>
|
||||||
Develop
|
Develop
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href={gistId ? `/deploy/${gistId}` : "/deploy"} passHref shallow>
|
<Link
|
||||||
<Button as="a" outline={!router.pathname.includes("/deploy")} uppercase>
|
href={gistId ? `/deploy/${gistId}` : "/deploy"}
|
||||||
|
passHref
|
||||||
|
shallow
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
as="a"
|
||||||
|
outline={!router.pathname.includes("/deploy")}
|
||||||
|
uppercase
|
||||||
|
>
|
||||||
Deploy
|
Deploy
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href={gistId ? `/test/${gistId}` : "/test"} passHref shallow>
|
<Link
|
||||||
<Button as="a" outline={!router.pathname.includes("/test")} uppercase>
|
href={gistId ? `/test/${gistId}` : "/test"}
|
||||||
|
passHref
|
||||||
|
shallow
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
as="a"
|
||||||
|
outline={!router.pathname.includes("/test")}
|
||||||
|
uppercase
|
||||||
|
>
|
||||||
Test
|
Test
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
<Button outline disabled>
|
<Link href="https://xrpl-hooks.readme.io/v2.0" passHref>
|
||||||
<BookOpen size="15px" />
|
<a target="_blank" rel="noreferrer noopener">
|
||||||
</Button>
|
<Button outline>
|
||||||
|
<BookOpen size="15px" />
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import Text from "./Text";
|
|||||||
const PanelBox = styled("div", {
|
const PanelBox = styled("div", {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
border: "1px solid $colors$mauve5",
|
border: "1px solid $colors$mauve6",
|
||||||
backgroundColor: "$mauve1",
|
backgroundColor: "$mauve2",
|
||||||
padding: "$3",
|
padding: "$3",
|
||||||
borderRadius: "$sm",
|
borderRadius: "$sm",
|
||||||
fontWeight: "lighter",
|
fontWeight: "lighter",
|
||||||
|
|||||||
109
components/Popover.tsx
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import React, { ReactNode } from "react";
|
||||||
|
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
||||||
|
import { styled, keyframes } from "../stitches.config";
|
||||||
|
|
||||||
|
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(PopoverPrimitive.Content, {
|
||||||
|
borderRadius: 4,
|
||||||
|
padding: "$3 $3",
|
||||||
|
fontSize: 12,
|
||||||
|
lineHeight: 1,
|
||||||
|
color: "$text",
|
||||||
|
backgroundColor: "$background",
|
||||||
|
boxShadow:
|
||||||
|
"0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2)",
|
||||||
|
"@media (prefers-reduced-motion: no-preference)": {
|
||||||
|
animationDuration: "400ms",
|
||||||
|
animationTimingFunction: "cubic-bezier(0.16, 1, 0.3, 1)",
|
||||||
|
willChange: "transform, opacity",
|
||||||
|
'&[data-state="open"]': {
|
||||||
|
'&[data-side="top"]': { animationName: slideDownAndFade },
|
||||||
|
'&[data-side="right"]': { animationName: slideLeftAndFade },
|
||||||
|
'&[data-side="bottom"]': { animationName: slideUpAndFade },
|
||||||
|
'&[data-side="left"]': { animationName: slideRightAndFade },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
".dark &": {
|
||||||
|
backgroundColor: "$mauve5",
|
||||||
|
boxShadow:
|
||||||
|
"0px 5px 38px -2px rgba(22, 23, 24, 1), 0px 10px 20px 0px rgba(22, 23, 24, 1)",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledArrow = styled(PopoverPrimitive.Arrow, {
|
||||||
|
fill: "$colors$mauve2",
|
||||||
|
".dark &": {
|
||||||
|
fill: "$mauve5",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledClose = styled(PopoverPrimitive.Close, {
|
||||||
|
all: "unset",
|
||||||
|
fontFamily: "inherit",
|
||||||
|
borderRadius: "100%",
|
||||||
|
height: 25,
|
||||||
|
width: 25,
|
||||||
|
display: "inline-flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
color: "$text",
|
||||||
|
position: "absolute",
|
||||||
|
top: 5,
|
||||||
|
right: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Exports
|
||||||
|
export const PopoverRoot = PopoverPrimitive.Root;
|
||||||
|
export const PopoverTrigger = PopoverPrimitive.Trigger;
|
||||||
|
export const PopoverContent = StyledContent;
|
||||||
|
export const PopoverArrow = StyledArrow;
|
||||||
|
export const PopoverClose = StyledClose;
|
||||||
|
|
||||||
|
interface IPopover {
|
||||||
|
content: string | ReactNode;
|
||||||
|
open?: boolean;
|
||||||
|
defaultOpen?: boolean;
|
||||||
|
onOpenChange?: (open: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Popover: React.FC<
|
||||||
|
IPopover & React.ComponentProps<typeof PopoverContent>
|
||||||
|
> = ({
|
||||||
|
children,
|
||||||
|
content,
|
||||||
|
open,
|
||||||
|
defaultOpen = false,
|
||||||
|
onOpenChange,
|
||||||
|
...rest
|
||||||
|
}) => (
|
||||||
|
<PopoverRoot
|
||||||
|
open={open}
|
||||||
|
defaultOpen={defaultOpen}
|
||||||
|
onOpenChange={onOpenChange}
|
||||||
|
>
|
||||||
|
<PopoverTrigger asChild>{children}</PopoverTrigger>
|
||||||
|
<PopoverContent sideOffset={5} {...rest}>
|
||||||
|
{content} <PopoverArrow offset={5} className="arrow" />
|
||||||
|
</PopoverContent>
|
||||||
|
</PopoverRoot>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Popover;
|
||||||
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;
|
||||||
127
components/Select.tsx
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
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 type { Props } from "react-select";
|
||||||
|
const SelectInput = dynamic(() => import("react-select"), { ssr: false });
|
||||||
|
|
||||||
|
// 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 ? 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
|
||||||
|
ref={ref}
|
||||||
|
menuPosition={props.menuPosition || "fixed"}
|
||||||
|
styles={{
|
||||||
|
container: (provided) => {
|
||||||
|
return {
|
||||||
|
...provided,
|
||||||
|
position: "relative",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
singleValue: (provided) => ({
|
||||||
|
...provided,
|
||||||
|
color: colors.mauve12,
|
||||||
|
}),
|
||||||
|
menu: (provided) => ({
|
||||||
|
...provided,
|
||||||
|
backgroundColor: colors.dropDownBg,
|
||||||
|
}),
|
||||||
|
control: (provided, state) => {
|
||||||
|
return {
|
||||||
|
...provided,
|
||||||
|
minHeight: 0,
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default styled(Select, {});
|
||||||
400
components/SetHookDialog.tsx
Normal file
@@ -0,0 +1,400 @@
|
|||||||
|
import React, { useCallback, useEffect, 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, Label } from "./Input";
|
||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
SubmitHandler,
|
||||||
|
useFieldArray,
|
||||||
|
useForm,
|
||||||
|
} from "react-hook-form";
|
||||||
|
|
||||||
|
import { TTS, tts } from "../utils/hookOnCalculator";
|
||||||
|
import { deployHook } from "../state/actions";
|
||||||
|
import { useSnapshot } from "valtio";
|
||||||
|
import state from "../state";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
import { prepareDeployHookTx, sha256 } from "../state/actions/deployHook";
|
||||||
|
import estimateFee from "../utils/estimateFee";
|
||||||
|
|
||||||
|
const transactionOptions = Object.keys(tts).map((key) => ({
|
||||||
|
label: key,
|
||||||
|
value: key as keyof TTS,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export type SetHookData = {
|
||||||
|
Invoke: {
|
||||||
|
value: keyof TTS;
|
||||||
|
label: string;
|
||||||
|
}[];
|
||||||
|
Fee: string;
|
||||||
|
HookNamespace: string;
|
||||||
|
HookParameters: {
|
||||||
|
HookParameter: {
|
||||||
|
HookParameterName: string;
|
||||||
|
HookParameterValue: string;
|
||||||
|
};
|
||||||
|
}[];
|
||||||
|
// HookGrants: {
|
||||||
|
// HookGrant: {
|
||||||
|
// Authorize: string;
|
||||||
|
// HookHash: string;
|
||||||
|
// };
|
||||||
|
// }[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
|
||||||
|
({ accountAddress }) => {
|
||||||
|
const snap = useSnapshot(state);
|
||||||
|
const account = snap.accounts.find((acc) => acc.address === accountAddress);
|
||||||
|
|
||||||
|
const [isSetHookDialogOpen, setIsSetHookDialogOpen] = useState(false);
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
control,
|
||||||
|
watch,
|
||||||
|
setValue,
|
||||||
|
getValues,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<SetHookData>({
|
||||||
|
defaultValues: {
|
||||||
|
HookNamespace:
|
||||||
|
snap.files?.[snap.activeWat]?.name?.split(".")?.[0] || "",
|
||||||
|
Invoke: transactionOptions.filter((to) => to.label === "ttPAYMENT"),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { fields, append, remove } = useFieldArray({
|
||||||
|
control,
|
||||||
|
name: "HookParameters", // unique name for your Field Array
|
||||||
|
});
|
||||||
|
const [formInitialized, setFormInitialized] = useState(false);
|
||||||
|
const [estimateLoading, setEstimateLoading] = useState(false);
|
||||||
|
|
||||||
|
// Update value if activeWat changes
|
||||||
|
useEffect(() => {
|
||||||
|
setValue(
|
||||||
|
"HookNamespace",
|
||||||
|
snap.files?.[snap.activeWat]?.name?.split(".")?.[0] || ""
|
||||||
|
);
|
||||||
|
setFormInitialized(true);
|
||||||
|
}, [snap.activeWat, snap.files, setValue]);
|
||||||
|
// const {
|
||||||
|
// fields: grantFields,
|
||||||
|
// append: grantAppend,
|
||||||
|
// remove: grantRemove,
|
||||||
|
// } = useFieldArray({
|
||||||
|
// control,
|
||||||
|
// name: "HookGrants", // unique name for your Field Array
|
||||||
|
// });
|
||||||
|
const [hashedNamespace, setHashedNamespace] = useState("");
|
||||||
|
const namespace = watch(
|
||||||
|
"HookNamespace",
|
||||||
|
snap.files?.[snap.active]?.name?.split(".")?.[0] || ""
|
||||||
|
);
|
||||||
|
const calculateHashedValue = useCallback(async () => {
|
||||||
|
const hashedVal = await sha256(namespace);
|
||||||
|
setHashedNamespace(hashedVal.toUpperCase());
|
||||||
|
}, [namespace]);
|
||||||
|
useEffect(() => {
|
||||||
|
calculateHashedValue();
|
||||||
|
}, [namespace, calculateHashedValue]);
|
||||||
|
|
||||||
|
// Calcucate initial fee estimate when modal opens
|
||||||
|
useEffect(() => {
|
||||||
|
if (formInitialized && account) {
|
||||||
|
(async () => {
|
||||||
|
const formValues = getValues();
|
||||||
|
const tx = await prepareDeployHookTx(account, formValues);
|
||||||
|
if (!tx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const res = await estimateFee(tx, account);
|
||||||
|
if (res && res.base_fee) {
|
||||||
|
setValue("Fee", res.base_fee);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [formInitialized]);
|
||||||
|
|
||||||
|
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>Hook Namespace Seed</Label>
|
||||||
|
<Input
|
||||||
|
{...register("HookNamespace", { required: true })}
|
||||||
|
autoComplete={"off"}
|
||||||
|
defaultValue={
|
||||||
|
snap.files?.[snap.activeWat]?.name?.split(".")?.[0] || ""
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{errors.HookNamespace?.type === "required" && (
|
||||||
|
<Box css={{ display: "inline", color: "$red11" }}>
|
||||||
|
Namespace is required
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<Box css={{ mt: "$3" }}>
|
||||||
|
<Label>Hook Namespace (sha256)</Label>
|
||||||
|
<Input readOnly value={hashedNamespace} />
|
||||||
|
</Box>
|
||||||
|
</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="Value (hex-quoted)"
|
||||||
|
{...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%", position: "relative" }}>
|
||||||
|
<Label>Fee</Label>
|
||||||
|
<Box css={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
{...register("Fee", { required: true })}
|
||||||
|
autoComplete={"off"}
|
||||||
|
defaultValue={10000}
|
||||||
|
css={{
|
||||||
|
"-moz-appearance": "textfield",
|
||||||
|
"&::-webkit-outer-spin-button": {
|
||||||
|
"-webkit-appearance": "none",
|
||||||
|
margin: 0,
|
||||||
|
},
|
||||||
|
"&::-webkit-inner-spin-button ": {
|
||||||
|
"-webkit-appearance": "none",
|
||||||
|
margin: 0,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="primary"
|
||||||
|
outline
|
||||||
|
isLoading={estimateLoading}
|
||||||
|
css={{
|
||||||
|
position: "absolute",
|
||||||
|
right: "$2",
|
||||||
|
fontSize: "$xs",
|
||||||
|
cursor: "pointer",
|
||||||
|
alignContent: "center",
|
||||||
|
display: "flex",
|
||||||
|
}}
|
||||||
|
onClick={async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setEstimateLoading(true);
|
||||||
|
const formValues = getValues();
|
||||||
|
try {
|
||||||
|
const tx = await prepareDeployHookTx(
|
||||||
|
account,
|
||||||
|
formValues
|
||||||
|
);
|
||||||
|
if (tx) {
|
||||||
|
const res = await estimateFee(tx, account);
|
||||||
|
|
||||||
|
if (res && res.base_fee) {
|
||||||
|
setValue("Fee", res.base_fee);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {}
|
||||||
|
|
||||||
|
setEstimateLoading(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Suggest
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
{errors.Fee?.type === "required" && (
|
||||||
|
<Box css={{ display: "inline", color: "$red11" }}>
|
||||||
|
Fee is required
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
SetHookDialog.displayName = "SetHookDialog";
|
||||||
|
|
||||||
|
export default SetHookDialog;
|
||||||
32
components/Switch.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { styled } from "../stitches.config";
|
||||||
|
import * as SwitchPrimitive from "@radix-ui/react-switch";
|
||||||
|
|
||||||
|
const StyledSwitch = styled(SwitchPrimitive.Root, {
|
||||||
|
all: "unset",
|
||||||
|
width: 42,
|
||||||
|
height: 25,
|
||||||
|
backgroundColor: "$mauve9",
|
||||||
|
borderRadius: "9999px",
|
||||||
|
position: "relative",
|
||||||
|
boxShadow: `0 2px 10px $colors$mauve2`,
|
||||||
|
WebkitTapHighlightColor: "rgba(0, 0, 0, 0)",
|
||||||
|
"&:focus": { boxShadow: `0 0 0 2px $colors$mauveA2` },
|
||||||
|
'&[data-state="checked"]': { backgroundColor: "$green11" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledThumb = styled(SwitchPrimitive.Thumb, {
|
||||||
|
display: "block",
|
||||||
|
width: 21,
|
||||||
|
height: 21,
|
||||||
|
backgroundColor: "white",
|
||||||
|
borderRadius: "9999px",
|
||||||
|
boxShadow: `0 2px 2px $colors$mauveA6`,
|
||||||
|
transition: "transform 100ms",
|
||||||
|
transform: "translateX(2px)",
|
||||||
|
willChange: "transform",
|
||||||
|
'&[data-state="checked"]': { transform: "translateX(19px)" },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Exports
|
||||||
|
export const Switch = StyledSwitch;
|
||||||
|
export const SwitchThumb = StyledThumb;
|
||||||
277
components/Tabs.tsx
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
import React, {
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
Fragment,
|
||||||
|
isValidElement,
|
||||||
|
useCallback,
|
||||||
|
} from "react";
|
||||||
|
import type { ReactNode, ReactElement } from "react";
|
||||||
|
import { Box, Button, Flex, Input, Label, 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 messages 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;
|
||||||
|
onChangeActive?: (index: number, header?: string) => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Tab = (props: TabProps) => null;
|
||||||
|
|
||||||
|
export const Tabs = ({
|
||||||
|
children,
|
||||||
|
activeIndex,
|
||||||
|
activeHeader,
|
||||||
|
headless,
|
||||||
|
keepAllAlive = false,
|
||||||
|
onCreateNewTab,
|
||||||
|
onCloseTab,
|
||||||
|
onChangeActive,
|
||||||
|
defaultExtension = "",
|
||||||
|
forceDefaultExtension,
|
||||||
|
}: Props) => {
|
||||||
|
const [active, setActive] = useState(activeIndex || 0);
|
||||||
|
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);
|
||||||
|
}, [activeIndex]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeHeader) {
|
||||||
|
const idx = tabs.findIndex(tab => tab.header === activeHeader);
|
||||||
|
if (idx !== -1) setActive(idx);
|
||||||
|
else setActive(0);
|
||||||
|
}
|
||||||
|
}, [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 handleActiveChange = useCallback(
|
||||||
|
(idx: number, header?: string) => {
|
||||||
|
setActive(idx);
|
||||||
|
onChangeActive?.(idx, header);
|
||||||
|
},
|
||||||
|
[onChangeActive]
|
||||||
|
);
|
||||||
|
|
||||||
|
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("");
|
||||||
|
|
||||||
|
onCreateNewTab?.(_tabname);
|
||||||
|
|
||||||
|
// switch to new tab?
|
||||||
|
handleActiveChange(tabs.length, _tabname);
|
||||||
|
}, [
|
||||||
|
tabname,
|
||||||
|
defaultExtension,
|
||||||
|
forceDefaultExtension,
|
||||||
|
validateTabname,
|
||||||
|
onCreateNewTab,
|
||||||
|
handleActiveChange,
|
||||||
|
tabs.length,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const handleCloseTab = useCallback(
|
||||||
|
(idx: number) => {
|
||||||
|
if (idx <= active && active !== 0) {
|
||||||
|
setActive(active - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
onCloseTab?.(idx, tabs[idx].header);
|
||||||
|
},
|
||||||
|
[active, onCloseTab, tabs]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!headless && (
|
||||||
|
<Stack
|
||||||
|
css={{
|
||||||
|
gap: "$3",
|
||||||
|
flex: 1,
|
||||||
|
flexWrap: "nowrap",
|
||||||
|
marginBottom: "$2",
|
||||||
|
width: "100%",
|
||||||
|
overflow: "auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tabs.map((tab, idx) => (
|
||||||
|
<Button
|
||||||
|
key={tab.header}
|
||||||
|
role="tab"
|
||||||
|
tabIndex={idx}
|
||||||
|
onClick={() => handleActiveChange(idx, tab.header)}
|
||||||
|
onKeyPress={() => handleActiveChange(idx, tab.header)}
|
||||||
|
outline={active !== idx}
|
||||||
|
size="sm"
|
||||||
|
css={{
|
||||||
|
"&:hover": {
|
||||||
|
span: {
|
||||||
|
visibility: "visible",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{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>
|
||||||
|
)}
|
||||||
|
{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>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -4,6 +4,23 @@ const Text = styled("span", {
|
|||||||
fontFamily: "$body",
|
fontFamily: "$body",
|
||||||
lineHeight: "$body",
|
lineHeight: "$body",
|
||||||
color: "$text",
|
color: "$text",
|
||||||
|
variants: {
|
||||||
|
small: {
|
||||||
|
true: {
|
||||||
|
fontSize: '$xs'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
true: {
|
||||||
|
color: '$mauve9'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
monospace: {
|
||||||
|
true: {
|
||||||
|
fontFamily: '$monospace'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default Text;
|
export default Text;
|
||||||
|
|||||||
92
components/Tooltip.tsx
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
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(0,0,0,.45), 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,.25), 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<
|
||||||
|
React.ComponentProps<typeof StyledContent> & ITooltip
|
||||||
|
> = ({
|
||||||
|
children,
|
||||||
|
content,
|
||||||
|
open,
|
||||||
|
defaultOpen = false,
|
||||||
|
onOpenChange,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<TooltipPrimitive.Root
|
||||||
|
open={open}
|
||||||
|
defaultOpen={defaultOpen}
|
||||||
|
onOpenChange={onOpenChange}
|
||||||
|
>
|
||||||
|
<TooltipPrimitive.Trigger asChild>{children}</TooltipPrimitive.Trigger>
|
||||||
|
<StyledContent side="bottom" align="center" {...rest}>
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: content }} />
|
||||||
|
<StyledArrow offset={5} width={11} height={5} />
|
||||||
|
</StyledContent>
|
||||||
|
</TooltipPrimitive.Root>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Tooltip;
|
||||||
184
components/Transaction/index.tsx
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
import { Play } from "phosphor-react";
|
||||||
|
import { FC, useCallback, useEffect, useMemo } from "react";
|
||||||
|
import { useSnapshot } from "valtio";
|
||||||
|
import state from "../../state";
|
||||||
|
import {
|
||||||
|
modifyTransaction,
|
||||||
|
prepareState,
|
||||||
|
prepareTransaction,
|
||||||
|
TransactionState,
|
||||||
|
} from "../../state/transactions";
|
||||||
|
import { sendTransaction } from "../../state/actions";
|
||||||
|
import Box from "../Box";
|
||||||
|
import Button from "../Button";
|
||||||
|
import Flex from "../Flex";
|
||||||
|
import { TxJson } from "./json";
|
||||||
|
import { TxUI } from "./ui";
|
||||||
|
|
||||||
|
export interface TransactionProps {
|
||||||
|
header: string;
|
||||||
|
state: TransactionState;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Transaction: FC<TransactionProps> = ({
|
||||||
|
header,
|
||||||
|
state: txState,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const { accounts, editorSettings } = useSnapshot(state);
|
||||||
|
const {
|
||||||
|
selectedAccount,
|
||||||
|
selectedTransaction,
|
||||||
|
txIsDisabled,
|
||||||
|
txIsLoading,
|
||||||
|
viewType,
|
||||||
|
editorSavedValue,
|
||||||
|
editorValue,
|
||||||
|
} = txState;
|
||||||
|
|
||||||
|
const setState = useCallback(
|
||||||
|
(pTx?: Partial<TransactionState>) => {
|
||||||
|
return modifyTransaction(header, pTx);
|
||||||
|
},
|
||||||
|
[header]
|
||||||
|
);
|
||||||
|
|
||||||
|
const prepareOptions = useCallback(
|
||||||
|
(state: TransactionState = txState) => {
|
||||||
|
const {
|
||||||
|
selectedTransaction,
|
||||||
|
selectedDestAccount,
|
||||||
|
selectedAccount,
|
||||||
|
txFields,
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
const TransactionType = selectedTransaction?.value || null;
|
||||||
|
const Destination =
|
||||||
|
selectedDestAccount?.value ||
|
||||||
|
("Destination" in txFields ? null : undefined);
|
||||||
|
const Account = selectedAccount?.value || null;
|
||||||
|
|
||||||
|
return prepareTransaction({
|
||||||
|
...txFields,
|
||||||
|
TransactionType,
|
||||||
|
Destination,
|
||||||
|
Account,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[txState]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const transactionType = selectedTransaction?.value;
|
||||||
|
const account = selectedAccount?.value;
|
||||||
|
if (!account || !transactionType || txIsLoading) {
|
||||||
|
setState({ txIsDisabled: true });
|
||||||
|
} else {
|
||||||
|
setState({ txIsDisabled: false });
|
||||||
|
}
|
||||||
|
}, [selectedAccount?.value, selectedTransaction?.value, setState, txIsLoading]);
|
||||||
|
|
||||||
|
const submitTest = useCallback(async () => {
|
||||||
|
let st: TransactionState | undefined;
|
||||||
|
if (viewType === "json") {
|
||||||
|
// save the editor state first
|
||||||
|
const pst = prepareState(editorValue || '', txState);
|
||||||
|
if (!pst) return;
|
||||||
|
|
||||||
|
st = setState(pst);
|
||||||
|
}
|
||||||
|
|
||||||
|
const account = accounts.find(
|
||||||
|
acc => acc.address === selectedAccount?.value
|
||||||
|
);
|
||||||
|
if (txIsDisabled) return;
|
||||||
|
|
||||||
|
setState({ txIsLoading: true });
|
||||||
|
const logPrefix = header ? `${header.split(".")[0]}: ` : undefined;
|
||||||
|
try {
|
||||||
|
if (!account) {
|
||||||
|
throw Error("Account must be selected from imported accounts!");
|
||||||
|
}
|
||||||
|
const options = prepareOptions(st);
|
||||||
|
|
||||||
|
if (options.Destination === null) {
|
||||||
|
throw Error("Destination account cannot be null")
|
||||||
|
}
|
||||||
|
|
||||||
|
await sendTransaction(account, options, { logPrefix });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
if (error instanceof Error) {
|
||||||
|
state.transactionLogs.push({
|
||||||
|
type: "error",
|
||||||
|
message: `${logPrefix}${error.message}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setState({ txIsLoading: false });
|
||||||
|
}, [viewType, accounts, txIsDisabled, setState, header, editorValue, txState, selectedAccount?.value, prepareOptions]);
|
||||||
|
|
||||||
|
const resetState = useCallback(() => {
|
||||||
|
modifyTransaction(header, { viewType }, { replaceState: true });
|
||||||
|
}, [header, viewType]);
|
||||||
|
|
||||||
|
const jsonValue = useMemo(
|
||||||
|
() =>
|
||||||
|
editorSavedValue ||
|
||||||
|
JSON.stringify(prepareOptions?.() || {}, null, editorSettings.tabSize),
|
||||||
|
[editorSavedValue, editorSettings.tabSize, prepareOptions]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box css={{ position: "relative", height: "calc(100% - 28px)" }} {...props}>
|
||||||
|
{viewType === "json" ? (
|
||||||
|
<TxJson
|
||||||
|
value={jsonValue}
|
||||||
|
header={header}
|
||||||
|
state={txState}
|
||||||
|
setState={setState}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<TxUI state={txState} setState={setState} />
|
||||||
|
)}
|
||||||
|
<Flex
|
||||||
|
row
|
||||||
|
css={{
|
||||||
|
justifyContent: "space-between",
|
||||||
|
position: "absolute",
|
||||||
|
left: 0,
|
||||||
|
bottom: 0,
|
||||||
|
width: "100%",
|
||||||
|
mb: "$1",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (viewType === "ui") {
|
||||||
|
setState({ editorSavedValue: null, viewType: "json" });
|
||||||
|
} else setState({ viewType: "ui" });
|
||||||
|
}}
|
||||||
|
outline
|
||||||
|
>
|
||||||
|
{viewType === "ui" ? "EDIT AS JSON" : "EXIT JSON MODE"}
|
||||||
|
</Button>
|
||||||
|
<Flex row>
|
||||||
|
<Button onClick={resetState} outline css={{ mr: "$3" }}>
|
||||||
|
RESET
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
onClick={submitTest}
|
||||||
|
isLoading={txIsLoading}
|
||||||
|
disabled={txIsDisabled}
|
||||||
|
>
|
||||||
|
<Play weight="bold" size="16px" />
|
||||||
|
RUN TEST
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Transaction;
|
||||||
205
components/Transaction/json.tsx
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
import Editor, { loader, useMonaco } from "@monaco-editor/react";
|
||||||
|
import { FC, useCallback, useEffect, useState } from "react";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
|
||||||
|
import dark from "../../theme/editor/amy.json";
|
||||||
|
import light from "../../theme/editor/xcode_default.json";
|
||||||
|
import { useSnapshot } from "valtio";
|
||||||
|
import state, {
|
||||||
|
prepareState,
|
||||||
|
transactionsData,
|
||||||
|
TransactionState,
|
||||||
|
} from "../../state";
|
||||||
|
import Text from "../Text";
|
||||||
|
import Flex from "../Flex";
|
||||||
|
import { Link } from "..";
|
||||||
|
import { showAlert } from "../../state/actions/showAlert";
|
||||||
|
import { parseJSON } from "../../utils/json";
|
||||||
|
import { extractSchemaProps } from "../../utils/schema";
|
||||||
|
import amountSchema from "../../content/amount-schema.json";
|
||||||
|
|
||||||
|
loader.config({
|
||||||
|
paths: {
|
||||||
|
vs: "https://cdn.jsdelivr.net/npm/monaco-editor@0.30.1/min/vs",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface JsonProps {
|
||||||
|
value?: string;
|
||||||
|
header?: string;
|
||||||
|
setState: (pTx?: Partial<TransactionState> | undefined) => void;
|
||||||
|
state: TransactionState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TxJson: FC<JsonProps> = ({
|
||||||
|
value = "",
|
||||||
|
state: txState,
|
||||||
|
header,
|
||||||
|
setState,
|
||||||
|
}) => {
|
||||||
|
const { editorSettings, accounts } = useSnapshot(state);
|
||||||
|
const { editorValue = value, selectedTransaction } = txState;
|
||||||
|
const { theme } = useTheme();
|
||||||
|
const [hasUnsaved, setHasUnsaved] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setState({ editorValue: value });
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (editorValue === value) setHasUnsaved(false);
|
||||||
|
else setHasUnsaved(true);
|
||||||
|
}, [editorValue, value]);
|
||||||
|
|
||||||
|
const saveState = (value: string, txState: TransactionState) => {
|
||||||
|
const tx = prepareState(value, txState);
|
||||||
|
if (tx) setState(tx);
|
||||||
|
};
|
||||||
|
|
||||||
|
const discardChanges = () => {
|
||||||
|
showAlert("Confirm", {
|
||||||
|
body: "Are you sure to discard these changes?",
|
||||||
|
confirmText: "Yes",
|
||||||
|
onConfirm: () => setState({ editorValue: value }),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onExit = (value: string) => {
|
||||||
|
const options = parseJSON(value);
|
||||||
|
if (options) {
|
||||||
|
saveState(value, txState);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showAlert("Error!", {
|
||||||
|
body: `Malformed Transaction in ${header}, would you like to discard these changes?`,
|
||||||
|
confirmText: "Discard",
|
||||||
|
onConfirm: () => setState({ editorValue: value }),
|
||||||
|
onCancel: () => setState({ viewType: "json", editorSavedValue: value }),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const path = `file:///${header}`;
|
||||||
|
const monaco = useMonaco();
|
||||||
|
|
||||||
|
const getSchemas = useCallback((): any[] => {
|
||||||
|
const tt = selectedTransaction?.value;
|
||||||
|
const txObj = transactionsData.find(td => td.TransactionType === tt);
|
||||||
|
|
||||||
|
let genericSchemaProps: any;
|
||||||
|
if (txObj) {
|
||||||
|
genericSchemaProps = extractSchemaProps(txObj);
|
||||||
|
} else {
|
||||||
|
genericSchemaProps = transactionsData.reduce(
|
||||||
|
(cumm, td) => ({
|
||||||
|
...cumm,
|
||||||
|
...extractSchemaProps(td),
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
uri: "file:///main-schema.json", // id of the first schema
|
||||||
|
fileMatch: ["**.json"], // associate with our model
|
||||||
|
schema: {
|
||||||
|
title: header,
|
||||||
|
type: "object",
|
||||||
|
required: ["TransactionType", "Account"],
|
||||||
|
properties: {
|
||||||
|
...genericSchemaProps,
|
||||||
|
TransactionType: {
|
||||||
|
title: "Transaction Type",
|
||||||
|
enum: transactionsData.map(td => td.TransactionType),
|
||||||
|
},
|
||||||
|
Account: {
|
||||||
|
$ref: "file:///account-schema.json",
|
||||||
|
},
|
||||||
|
Destination: {
|
||||||
|
anyOf: [
|
||||||
|
{
|
||||||
|
$ref: "file:///account-schema.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "string",
|
||||||
|
title: "Destination Account",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Amount: {
|
||||||
|
$ref: "file:///amount-schema.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: "file:///account-schema.json",
|
||||||
|
schema: {
|
||||||
|
type: "string",
|
||||||
|
title: "Account type",
|
||||||
|
enum: accounts.map(acc => acc.address),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...amountSchema,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, [accounts, header, selectedTransaction?.value]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!monaco) return;
|
||||||
|
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
|
||||||
|
validate: true,
|
||||||
|
schemas: getSchemas(),
|
||||||
|
});
|
||||||
|
}, [getSchemas, monaco]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
fluid
|
||||||
|
column
|
||||||
|
css={{ height: "calc(100% - 45px)", position: "relative" }}
|
||||||
|
>
|
||||||
|
<Editor
|
||||||
|
className="hooks-editor"
|
||||||
|
language={"json"}
|
||||||
|
path={path}
|
||||||
|
height="100%"
|
||||||
|
beforeMount={monaco => {
|
||||||
|
monaco.editor.defineTheme("dark", dark as any);
|
||||||
|
monaco.editor.defineTheme("light", light as any);
|
||||||
|
}}
|
||||||
|
value={editorValue}
|
||||||
|
onChange={val => setState({ editorValue: val })}
|
||||||
|
onMount={(editor, monaco) => {
|
||||||
|
editor.updateOptions({
|
||||||
|
minimap: { enabled: false },
|
||||||
|
glyphMargin: true,
|
||||||
|
tabSize: editorSettings.tabSize,
|
||||||
|
dragAndDrop: true,
|
||||||
|
fontSize: 14,
|
||||||
|
});
|
||||||
|
|
||||||
|
// register onExit cb
|
||||||
|
const model = editor.getModel();
|
||||||
|
model?.onWillDispose(() => onExit(model.getValue()));
|
||||||
|
|
||||||
|
// set json defaults
|
||||||
|
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
|
||||||
|
validate: true,
|
||||||
|
schemas: getSchemas(),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
theme={theme === "dark" ? "dark" : "light"}
|
||||||
|
/>
|
||||||
|
{hasUnsaved && (
|
||||||
|
<Text muted small css={{ position: "absolute", bottom: 0, right: 0 }}>
|
||||||
|
This file has unsaved changes.{" "}
|
||||||
|
<Link onClick={() => saveState(editorValue, txState)}>save</Link>{" "}
|
||||||
|
<Link onClick={discardChanges}>discard</Link>
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
213
components/Transaction/ui.tsx
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
import { FC } from "react";
|
||||||
|
import Container from "../Container";
|
||||||
|
import Flex from "../Flex";
|
||||||
|
import Input from "../Input";
|
||||||
|
import Select from "../Select";
|
||||||
|
import Text from "../Text";
|
||||||
|
import {
|
||||||
|
SelectOption,
|
||||||
|
TransactionState,
|
||||||
|
transactionsData,
|
||||||
|
TxFields,
|
||||||
|
} from "../../state/transactions";
|
||||||
|
import { useSnapshot } from "valtio";
|
||||||
|
import state from "../../state";
|
||||||
|
import { streamState } from "../DebugStream";
|
||||||
|
|
||||||
|
interface UIProps {
|
||||||
|
setState: (pTx?: Partial<TransactionState> | undefined) => void;
|
||||||
|
state: TransactionState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TxUI: FC<UIProps> = ({ state: txState, setState }) => {
|
||||||
|
const { accounts } = useSnapshot(state);
|
||||||
|
const {
|
||||||
|
selectedAccount,
|
||||||
|
selectedDestAccount,
|
||||||
|
selectedTransaction,
|
||||||
|
txFields,
|
||||||
|
} = txState;
|
||||||
|
|
||||||
|
const transactionsOptions = transactionsData.map(tx => ({
|
||||||
|
value: tx.TransactionType,
|
||||||
|
label: tx.TransactionType,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const accountOptions: SelectOption[] = accounts.map(acc => ({
|
||||||
|
label: acc.name,
|
||||||
|
value: acc.address,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const destAccountOptions: SelectOption[] = accounts
|
||||||
|
.map(acc => ({
|
||||||
|
label: acc.name,
|
||||||
|
value: acc.address,
|
||||||
|
}))
|
||||||
|
.filter(acc => acc.value !== selectedAccount?.value);
|
||||||
|
|
||||||
|
const resetOptions = (tt: string) => {
|
||||||
|
const txFields: TxFields | undefined = transactionsData.find(
|
||||||
|
tx => tx.TransactionType === tt
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!txFields) return setState({ txFields: {} });
|
||||||
|
|
||||||
|
const _txFields = Object.keys(txFields)
|
||||||
|
.filter(key => !["TransactionType", "Account", "Sequence"].includes(key))
|
||||||
|
.reduce<TxFields>(
|
||||||
|
(tf, key) => ((tf[key as keyof TxFields] = (txFields as any)[key]), tf),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!_txFields.Destination) setState({ selectedDestAccount: null });
|
||||||
|
setState({ txFields: _txFields });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSetAccount = (acc: SelectOption) => {
|
||||||
|
setState({ selectedAccount: acc });
|
||||||
|
streamState.selectedAccount = acc;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangeTxType = (tt: SelectOption) => {
|
||||||
|
setState({ selectedTransaction: tt });
|
||||||
|
resetOptions(tt.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const specialFields = ["TransactionType", "Account", "Destination"];
|
||||||
|
|
||||||
|
const otherFields = Object.keys(txFields).filter(
|
||||||
|
k => !specialFields.includes(k)
|
||||||
|
) as [keyof TxFields];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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",
|
||||||
|
mt: "1px",
|
||||||
|
pr: "1px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text muted css={{ mr: "$3" }}>
|
||||||
|
Transaction type:{" "}
|
||||||
|
</Text>
|
||||||
|
<Select
|
||||||
|
instanceId="transactionsType"
|
||||||
|
placeholder="Select transaction type"
|
||||||
|
options={transactionsOptions}
|
||||||
|
hideSelectedOptions
|
||||||
|
css={{ width: "70%" }}
|
||||||
|
value={selectedTransaction}
|
||||||
|
onChange={(tt: any) => handleChangeTxType(tt)}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
row
|
||||||
|
fluid
|
||||||
|
css={{
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
alignItems: "center",
|
||||||
|
mb: "$3",
|
||||||
|
pr: "1px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text muted css={{ mr: "$3" }}>
|
||||||
|
Account:{" "}
|
||||||
|
</Text>
|
||||||
|
<Select
|
||||||
|
instanceId="from-account"
|
||||||
|
placeholder="Select your account"
|
||||||
|
css={{ width: "70%" }}
|
||||||
|
options={accountOptions}
|
||||||
|
value={selectedAccount}
|
||||||
|
onChange={(acc: any) => handleSetAccount(acc)} // TODO make react-select have correct types for acc
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
{txFields.Destination !== undefined && (
|
||||||
|
<Flex
|
||||||
|
row
|
||||||
|
fluid
|
||||||
|
css={{
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
alignItems: "center",
|
||||||
|
mb: "$3",
|
||||||
|
pr: "1px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text muted css={{ mr: "$3" }}>
|
||||||
|
Destination account:{" "}
|
||||||
|
</Text>
|
||||||
|
<Select
|
||||||
|
instanceId="to-account"
|
||||||
|
placeholder="Select the destination account"
|
||||||
|
css={{ width: "70%" }}
|
||||||
|
options={destAccountOptions}
|
||||||
|
value={selectedDestAccount}
|
||||||
|
isClearable
|
||||||
|
onChange={(acc: any) => setState({ selectedDestAccount: acc })}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
{otherFields.map(field => {
|
||||||
|
let _value = txFields[field];
|
||||||
|
|
||||||
|
let value: string | undefined;
|
||||||
|
if (typeof _value === "object") {
|
||||||
|
if (_value.$type === "json" && typeof _value.$value === "object") {
|
||||||
|
value = JSON.stringify(_value.$value);
|
||||||
|
} else {
|
||||||
|
value = _value.$value.toString();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = _value?.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
let isXrp = typeof _value === "object" && _value.$type === "xrp";
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
key={field}
|
||||||
|
row
|
||||||
|
fluid
|
||||||
|
css={{
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
alignItems: "center",
|
||||||
|
mb: "$3",
|
||||||
|
pr: "1px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text muted css={{ mr: "$3" }}>
|
||||||
|
{field + (isXrp ? " (XRP)" : "")}:{" "}
|
||||||
|
</Text>
|
||||||
|
<Input
|
||||||
|
value={value}
|
||||||
|
onChange={e => {
|
||||||
|
setState({
|
||||||
|
txFields: {
|
||||||
|
...txFields,
|
||||||
|
[field]:
|
||||||
|
typeof _value === "object"
|
||||||
|
? { ..._value, $value: e.target.value }
|
||||||
|
: e.target.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
css={{ width: "70%", flex: "inherit" }}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
40
components/icons/Carbon.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
const Carbon = () => (
|
||||||
|
<svg
|
||||||
|
width="66"
|
||||||
|
height="32"
|
||||||
|
viewBox="0 0 66 32"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M33 2L23 15H28L21 24H45L38 15H43L33 2Z"
|
||||||
|
stroke="#EDEDEF"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M33 24V30"
|
||||||
|
stroke="#EDEDEF"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="angle"
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M-1.14441e-05 4L8.94099 15.0625L4.00543e-05 26.125H2.27587L10.5015 15.9475H16.5938V14.1775H10.5015L2.27582 4H-1.14441e-05Z"
|
||||||
|
fill="#EDEDEF"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="angle"
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M66 4L57.059 15.0625L66 26.125H63.7241L55.4985 15.9475H49.4062V14.1775H55.4985L63.7242 4H66Z"
|
||||||
|
fill="#EDEDEF"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Carbon;
|
||||||
75
components/icons/Firewall.tsx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
const Firewall = () => (
|
||||||
|
<svg
|
||||||
|
width="66"
|
||||||
|
height="32"
|
||||||
|
viewBox="0 0 66 32"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M33 13V7"
|
||||||
|
stroke="#EDEDEF"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M27 19V13"
|
||||||
|
stroke="#EDEDEF"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M39 19V13"
|
||||||
|
stroke="#EDEDEF"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M33 25V19"
|
||||||
|
stroke="#EDEDEF"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M21 13H45"
|
||||||
|
stroke="#EDEDEF"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M21 19H45"
|
||||||
|
stroke="#EDEDEF"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M45 7H21V25H45V7Z"
|
||||||
|
stroke="#EDEDEF"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="angle"
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M-1.14441e-05 4.875L8.94099 15.9375L4.00543e-05 27H2.27587L10.5015 16.8225H16.5938V15.0525H10.5015L2.27582 4.875H-1.14441e-05Z"
|
||||||
|
fill="#EDEDEF"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="angle"
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M66 4.875L57.059 15.9375L66 27H63.7241L55.4985 16.8225H49.4062V15.0525H55.4985L63.7242 4.875H66Z"
|
||||||
|
fill="#EDEDEF"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Firewall;
|
||||||
40
components/icons/Notary.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
const Notary = () => (
|
||||||
|
<svg
|
||||||
|
width="66"
|
||||||
|
height="32"
|
||||||
|
viewBox="0 0 66 32"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M37.5 10.5L26.5 21.5L21 16.0002"
|
||||||
|
stroke="#EDEDEF"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M49 10.5L38 21.5L35.0784 18.5785"
|
||||||
|
stroke="#EDEDEF"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="angle"
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M-1.14441e-05 5L8.94099 16.0625L4.00543e-05 27.125H2.27587L10.5015 16.9475H16.5938V15.1775H10.5015L2.27582 5H-1.14441e-05Z"
|
||||||
|
fill="#EDEDEF"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="angle"
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M66 5L57.059 16.0625L66 27.125H63.7241L55.4985 16.9475H49.4062V15.1775H55.4985L63.7242 5H66Z"
|
||||||
|
fill="#EDEDEF"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Notary;
|
||||||
61
components/icons/Peggy.tsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
const Peggy = () => (
|
||||||
|
<svg
|
||||||
|
width="66"
|
||||||
|
height="32"
|
||||||
|
viewBox="0 0 66 32"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M33 19C40.1797 19 46 16.3137 46 13C46 9.68629 40.1797 7 33 7C25.8203 7 20 9.68629 20 13C20 16.3137 25.8203 19 33 19Z"
|
||||||
|
stroke="#EDEDEF"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M33 19V25"
|
||||||
|
stroke="#EDEDEF"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M20 13V19C20 22 25 25 33 25C41 25 46 22 46 19V13"
|
||||||
|
stroke="#EDEDEF"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M41 17.7633V23.7634"
|
||||||
|
stroke="#EDEDEF"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M25 17.7633V23.7634"
|
||||||
|
stroke="#EDEDEF"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="angle"
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M-1.14441e-05 4L8.94099 15.0625L4.00543e-05 26.125H2.27587L10.5015 15.9475H16.5938V14.1775H10.5015L2.27582 4H-1.14441e-05Z"
|
||||||
|
fill="#EDEDEF"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="angle"
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M66 4L57.059 15.0625L66 26.125H63.7241L55.4985 15.9475H49.4062V14.1775H55.4985L63.7242 4H66Z"
|
||||||
|
fill="#EDEDEF"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Peggy;
|
||||||
40
components/icons/Starter.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
const Starter = () => (
|
||||||
|
<svg
|
||||||
|
width="66"
|
||||||
|
height="32"
|
||||||
|
viewBox="0 0 66 32"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M42 28H24C23.7347 28 23.4804 27.8946 23.2929 27.7071C23.1053 27.5196 23 27.2652 23 27V5C23 4.73479 23.1053 4.48044 23.2929 4.2929C23.4804 4.10537 23.7347 4.00001 24 4H36.0003L43 11V27C43 27.2652 42.8947 27.5196 42.7071 27.7071C42.5196 27.8946 42.2653 28 42 28V28Z"
|
||||||
|
stroke="#EDEDEF"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M36 4V11H43.001"
|
||||||
|
stroke="#EDEDEF"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="angle"
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M-1.14441e-05 4.875L8.94099 15.9375L4.00543e-05 27H2.27587L10.5015 16.8225H16.5938V15.0525H10.5015L2.27582 4.875H-1.14441e-05Z"
|
||||||
|
fill="#EDEDEF"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="angle"
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M66 4.875L57.059 15.9375L66 27H63.7241L55.4985 16.8225H49.4062V15.0525H55.4985L63.7242 4.875H66Z"
|
||||||
|
fill="#EDEDEF"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Starter;
|
||||||
17
components/index.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
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, Label } from "./Input";
|
||||||
|
export { default as Select } from "./Select";
|
||||||
|
export * from "./Tabs";
|
||||||
|
export * from "./AlertDialog/primitive";
|
||||||
|
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";
|
||||||
50
content/amount-schema.json
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"uri": "file:///amount-schema.json",
|
||||||
|
"title": "Amount",
|
||||||
|
"description": "Specify xrp in drops and tokens as objects.",
|
||||||
|
"schema": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"number",
|
||||||
|
"string"
|
||||||
|
],
|
||||||
|
"exclusiveMinimum": 0,
|
||||||
|
"maximum": "100000000000000000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"currency": {
|
||||||
|
"description": "Arbitrary currency code for the token. Cannot be XRP."
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"number"
|
||||||
|
],
|
||||||
|
"description": "Quoted decimal representation of the amount of the token."
|
||||||
|
},
|
||||||
|
"issuer": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Generally, the account that issues this token. In special cases, this can refer to the account that holds the token instead."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"defaultSnippets": [
|
||||||
|
{
|
||||||
|
"label": "Xrp",
|
||||||
|
"body": "1000000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Token",
|
||||||
|
"body": {
|
||||||
|
"currency": "${1:13.1}",
|
||||||
|
"value": "${2:FOO}",
|
||||||
|
"description": "${3:rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpns}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
221
content/transactions.json
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"TransactionType": "AccountDelete",
|
||||||
|
"Account": "rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm",
|
||||||
|
"Destination": "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe",
|
||||||
|
"DestinationTag": 13,
|
||||||
|
"Fee": "2000000",
|
||||||
|
"Sequence": 2470665,
|
||||||
|
"Flags": 2147483648
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TransactionType": "AccountSet",
|
||||||
|
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
|
"Fee": "12",
|
||||||
|
"Sequence": 5,
|
||||||
|
"Domain": "6578616D706C652E636F6D",
|
||||||
|
"SetFlag": 5,
|
||||||
|
"MessageKey": "03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Account": "rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo",
|
||||||
|
"TransactionType": "CheckCancel",
|
||||||
|
"CheckID": "49647F0D748DC3FE26BDACBC57F251AADEFFF391403EC9BF87C97F67E9977FB0",
|
||||||
|
"Fee": "12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Account": "rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy",
|
||||||
|
"TransactionType": "CheckCash",
|
||||||
|
"Amount": {
|
||||||
|
"$value": "100",
|
||||||
|
"$type": "xrp"
|
||||||
|
},
|
||||||
|
"CheckID": "838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334",
|
||||||
|
"Fee": "12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TransactionType": "CheckCreate",
|
||||||
|
"Account": "rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo",
|
||||||
|
"Destination": "rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy",
|
||||||
|
"SendMax": "100000000",
|
||||||
|
"Expiration": 570113521,
|
||||||
|
"InvoiceID": "6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B",
|
||||||
|
"DestinationTag": 1,
|
||||||
|
"Fee": "12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TransactionType": "DepositPreauth",
|
||||||
|
"Account": "rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8",
|
||||||
|
"Authorize": "rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de",
|
||||||
|
"Fee": "10",
|
||||||
|
"Flags": 2147483648,
|
||||||
|
"Sequence": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
|
"TransactionType": "EscrowCancel",
|
||||||
|
"Owner": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
|
"OfferSequence": 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
|
"TransactionType": "EscrowCreate",
|
||||||
|
"Amount": {
|
||||||
|
"$value": "100",
|
||||||
|
"$type": "xrp"
|
||||||
|
},
|
||||||
|
"Destination": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
|
||||||
|
"CancelAfter": 533257958,
|
||||||
|
"FinishAfter": 533171558,
|
||||||
|
"Condition": "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100",
|
||||||
|
"DestinationTag": 23480,
|
||||||
|
"SourceTag": 11747
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
|
"TransactionType": "EscrowFinish",
|
||||||
|
"Owner": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
|
"OfferSequence": 7,
|
||||||
|
"Condition": "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100",
|
||||||
|
"Fulfillment": "A0028000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TransactionType": "NFTokenBurn",
|
||||||
|
"Account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||||
|
"Fee": "10",
|
||||||
|
"TokenID": "000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TransactionType": "NFTokenAcceptOffer",
|
||||||
|
"Fee": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TransactionType": "NFTokenCancelOffer",
|
||||||
|
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
||||||
|
"TokenIDs": "000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TransactionType": "NFTokenCreateOffer",
|
||||||
|
"Account": "rs8jBmmfpwgmrSPgwMsh7CvKRmRt1JTVSX",
|
||||||
|
"TokenID": "000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007",
|
||||||
|
"Amount": {
|
||||||
|
"$value": "100",
|
||||||
|
"$type": "xrp"
|
||||||
|
},
|
||||||
|
"Flags": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TransactionType": "OfferCancel",
|
||||||
|
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
||||||
|
"Fee": "12",
|
||||||
|
"Flags": 0,
|
||||||
|
"LastLedgerSequence": 7108629,
|
||||||
|
"OfferSequence": 6,
|
||||||
|
"Sequence": 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TransactionType": "OfferCreate",
|
||||||
|
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
||||||
|
"Fee": "12",
|
||||||
|
"Flags": 0,
|
||||||
|
"LastLedgerSequence": 7108682,
|
||||||
|
"Sequence": 8,
|
||||||
|
"TakerGets": "6000000",
|
||||||
|
"Amount": {
|
||||||
|
"$value": "100",
|
||||||
|
"$type": "xrp"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TransactionType": "Payment",
|
||||||
|
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
|
"Destination": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
||||||
|
"Amount": {
|
||||||
|
"$value": "100",
|
||||||
|
"$type": "xrp"
|
||||||
|
},
|
||||||
|
"Fee": "12",
|
||||||
|
"Flags": 2147483648,
|
||||||
|
"Sequence": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
|
"TransactionType": "PaymentChannelCreate",
|
||||||
|
"Amount": {
|
||||||
|
"$value": "100",
|
||||||
|
"$type": "xrp"
|
||||||
|
},
|
||||||
|
"Destination": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
|
||||||
|
"SettleDelay": 86400,
|
||||||
|
"PublicKey": "32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A",
|
||||||
|
"CancelAfter": 533171558,
|
||||||
|
"DestinationTag": 23480,
|
||||||
|
"SourceTag": 11747
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
|
"TransactionType": "PaymentChannelFund",
|
||||||
|
"Channel": "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198",
|
||||||
|
"Amount": {
|
||||||
|
"$value": "200",
|
||||||
|
"$type": "xrp"
|
||||||
|
},
|
||||||
|
"Expiration": 543171558
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Flags": 0,
|
||||||
|
"TransactionType": "SetRegularKey",
|
||||||
|
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
|
"Fee": "12",
|
||||||
|
"RegularKey": "rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Flags": 0,
|
||||||
|
"TransactionType": "SignerListSet",
|
||||||
|
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
|
"Fee": "12",
|
||||||
|
"SignerQuorum": 3,
|
||||||
|
"SignerEntries": {
|
||||||
|
"$type": "json",
|
||||||
|
"$value": [
|
||||||
|
{
|
||||||
|
"SignerEntry": {
|
||||||
|
"Account": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
|
||||||
|
"SignerWeight": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"SignerEntry": {
|
||||||
|
"Account": "rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v",
|
||||||
|
"SignerWeight": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"SignerEntry": {
|
||||||
|
"Account": "raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n",
|
||||||
|
"SignerWeight": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TransactionType": "TicketCreate",
|
||||||
|
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||||
|
"Fee": "10",
|
||||||
|
"Sequence": 381,
|
||||||
|
"TicketCount": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TransactionType": "TrustSet",
|
||||||
|
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
||||||
|
"Fee": "12",
|
||||||
|
"Flags": 262144,
|
||||||
|
"LastLedgerSequence": 8007750,
|
||||||
|
"Amount": {
|
||||||
|
"$value": "100",
|
||||||
|
"$type": "xrp"
|
||||||
|
},
|
||||||
|
"Sequence": 12
|
||||||
|
}
|
||||||
|
]
|
||||||
1
next-env.d.ts
vendored
@@ -1,5 +1,4 @@
|
|||||||
/// <reference types="next" />
|
/// <reference types="next" />
|
||||||
/// <reference types="next/types/global" />
|
|
||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// NOTE: This file should not be edited
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ module.exports = {
|
|||||||
if (!isServer) {
|
if (!isServer) {
|
||||||
config.resolve.fallback.fs = false;
|
config.resolve.fallback.fs = false;
|
||||||
}
|
}
|
||||||
|
config.module.rules.push({
|
||||||
|
test: /\.md$/,
|
||||||
|
use: "raw-loader",
|
||||||
|
});
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
32
package.json
@@ -12,22 +12,30 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codingame/monaco-jsonrpc": "^0.3.1",
|
"@codingame/monaco-jsonrpc": "^0.3.1",
|
||||||
"@codingame/monaco-languageclient": "^0.17.0",
|
"@codingame/monaco-languageclient": "^0.17.0",
|
||||||
"@monaco-editor/react": "^4.3.1",
|
"@monaco-editor/react": "^4.4.1",
|
||||||
"@octokit/core": "^3.5.1",
|
"@octokit/core": "^3.5.1",
|
||||||
"@radix-ui/colors": "^0.1.7",
|
"@radix-ui/colors": "^0.1.7",
|
||||||
"@radix-ui/react-alert-dialog": "^0.1.1",
|
"@radix-ui/react-alert-dialog": "^0.1.1",
|
||||||
"@radix-ui/react-dialog": "^0.1.1",
|
"@radix-ui/react-dialog": "^0.1.1",
|
||||||
"@radix-ui/react-dropdown-menu": "^0.1.1",
|
"@radix-ui/react-dropdown-menu": "^0.1.1",
|
||||||
"@radix-ui/react-id": "^0.1.1",
|
"@radix-ui/react-id": "^0.1.1",
|
||||||
|
"@radix-ui/react-label": "^0.1.5",
|
||||||
|
"@radix-ui/react-popover": "^0.1.6",
|
||||||
|
"@radix-ui/react-switch": "^0.1.5",
|
||||||
|
"@radix-ui/react-tooltip": "^0.1.7",
|
||||||
"@stitches/react": "^1.2.6-0",
|
"@stitches/react": "^1.2.6-0",
|
||||||
"file-saver": "^2.0.5",
|
|
||||||
"jszip": "^3.7.1",
|
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
"monaco-editor": "^0.30.1",
|
"file-saver": "^2.0.5",
|
||||||
|
"filesize": "^8.0.7",
|
||||||
|
"javascript-time-ago": "^2.3.11",
|
||||||
|
"jszip": "^3.7.1",
|
||||||
|
"lodash.uniqby": "^4.7.0",
|
||||||
|
"lodash.xor": "^4.5.0",
|
||||||
|
"monaco-editor": "^0.33.0",
|
||||||
"next": "^12.0.4",
|
"next": "^12.0.4",
|
||||||
"next-auth": "^4.0.0-beta.5",
|
"next-auth": "^4.0.0-beta.5",
|
||||||
"next-themes": "^0.0.15",
|
"next-themes": "^0.1.1",
|
||||||
"normalize-url": "^7.0.2",
|
"normalize-url": "^7.0.2",
|
||||||
"octokit": "^1.7.0",
|
"octokit": "^1.7.0",
|
||||||
"pako": "^2.0.4",
|
"pako": "^2.0.4",
|
||||||
@@ -37,25 +45,33 @@
|
|||||||
"re-resizable": "^6.9.1",
|
"re-resizable": "^6.9.1",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
|
"react-hook-form": "^7.28.0",
|
||||||
"react-hot-keys": "^2.7.1",
|
"react-hot-keys": "^2.7.1",
|
||||||
"react-hot-toast": "^2.1.1",
|
"react-hot-toast": "^2.1.1",
|
||||||
"react-new-window": "^0.2.1",
|
"react-new-window": "^0.2.1",
|
||||||
|
"react-select": "^5.2.1",
|
||||||
|
"react-split": "^2.0.14",
|
||||||
"react-stay-scrolled": "^7.4.0",
|
"react-stay-scrolled": "^7.4.0",
|
||||||
|
"react-time-ago": "^7.1.9",
|
||||||
"reconnecting-websocket": "^4.4.0",
|
"reconnecting-websocket": "^4.4.0",
|
||||||
|
"regexify-string": "^1.0.17",
|
||||||
"valtio": "^1.2.5",
|
"valtio": "^1.2.5",
|
||||||
"vscode-languageserver": "^7.0.0",
|
"vscode-languageserver": "^7.0.0",
|
||||||
"vscode-uri": "^3.0.2",
|
"vscode-uri": "^3.0.2",
|
||||||
"wabt": "1.0.16",
|
"wabt": "1.0.16",
|
||||||
"xrpl-accountlib": "^1.2.3",
|
"xrpl-accountlib": "^1.3.2",
|
||||||
"xrpl-client": "^1.9.3"
|
"xrpl-client": "^1.9.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/file-saver": "^2.0.4",
|
|
||||||
"@types/dinero.js": "^1.9.0",
|
"@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/pako": "^1.0.2",
|
||||||
"@types/react": "17.0.31",
|
"@types/react": "17.0.31",
|
||||||
"eslint": "7.32.0",
|
"eslint": "7.32.0",
|
||||||
"eslint-config-next": "11.1.2",
|
"eslint-config-next": "11.1.2",
|
||||||
|
"raw-loader": "^4.0.2",
|
||||||
"typescript": "4.4.4"
|
"typescript": "4.4.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,24 +13,106 @@ import Navigation from "../components/Navigation";
|
|||||||
import { fetchFiles } from "../state/actions";
|
import { fetchFiles } from "../state/actions";
|
||||||
import state from "../state";
|
import state from "../state";
|
||||||
|
|
||||||
|
import TimeAgo from "javascript-time-ago";
|
||||||
|
import en from "javascript-time-ago/locale/en.json";
|
||||||
|
import { useSnapshot } from "valtio";
|
||||||
|
import Alert from "../components/AlertDialog";
|
||||||
|
|
||||||
|
TimeAgo.setDefaultLocale(en.locale);
|
||||||
|
TimeAgo.addLocale(en);
|
||||||
|
|
||||||
function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
|
function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const slug = router.query?.slug;
|
const slug = router.query?.slug;
|
||||||
const gistId = (Array.isArray(slug) && slug[0]) ?? null;
|
const gistId = (Array.isArray(slug) && slug[0]) ?? null;
|
||||||
|
|
||||||
|
const origin = "https://xrpl-hooks-ide.vercel.app"; // TODO: Change when site is deployed
|
||||||
|
const shareImg = "/share-image.png";
|
||||||
|
|
||||||
|
const snap = useSnapshot(state);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (gistId && router.isReady) {
|
if (gistId && router.isReady) {
|
||||||
fetchFiles(gistId);
|
fetchFiles(gistId);
|
||||||
} else {
|
} 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.mainModalOpen = true;
|
||||||
|
state.mainModalShowed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [gistId, router.isReady, router.pathname]);
|
}, [
|
||||||
|
gistId,
|
||||||
|
router.isReady,
|
||||||
|
router.pathname,
|
||||||
|
snap.files,
|
||||||
|
snap.mainModalShowed,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<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 Builder</title>
|
||||||
|
<meta property="og:title" content="XRPL Hooks Builder" />
|
||||||
|
<meta name="twitter:title" content="XRPL Hooks Builder" />
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta name="twitter:site" content="@XRPLF" />
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Hooks Builder, add smart contract functionality to the XRP Ledger."
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
property="og:description"
|
||||||
|
content="Hooks Builder, add smart contract functionality to the XRP Ledger."
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
name="twitter:description"
|
||||||
|
content="Hooks Builder, 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 Builder" />
|
||||||
|
<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>
|
</Head>
|
||||||
<IdProvider>
|
<IdProvider>
|
||||||
<SessionProvider session={session}>
|
<SessionProvider session={session}>
|
||||||
@@ -59,6 +141,7 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
|
|||||||
})(),
|
})(),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Alert />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</SessionProvider>
|
</SessionProvider>
|
||||||
</IdProvider>
|
</IdProvider>
|
||||||
|
|||||||
@@ -16,11 +16,14 @@ class MyDocument extends Document {
|
|||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
globalStyles();
|
globalStyles();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Html>
|
<Html>
|
||||||
<Head>
|
<Head>
|
||||||
<meta name="description" content="Playground for XRPL Hooks" />
|
<style
|
||||||
<link rel="icon" href="/favicon.ico" />
|
id="stitches"
|
||||||
|
dangerouslySetInnerHTML={{ __html: getCssText() }}
|
||||||
|
/>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link
|
<link
|
||||||
rel="preconnect"
|
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"
|
href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital@0;1&family=Work+Sans:wght@400;600;700&display=swap"
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
<style
|
|
||||||
id="stitches"
|
|
||||||
dangerouslySetInnerHTML={{ __html: getCssText() }}
|
|
||||||
/>
|
|
||||||
</Head>
|
</Head>
|
||||||
<body>
|
<body>
|
||||||
<Main />
|
<Main />
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import { NextResponse as Response } from 'next/server';
|
|||||||
export default function middleware(req: NextRequest, ev: NextFetchEvent) {
|
export default function middleware(req: NextRequest, ev: NextFetchEvent) {
|
||||||
|
|
||||||
if (req.nextUrl.pathname === "/") {
|
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') {
|
if (req.method !== 'POST') {
|
||||||
return res.status(405).json({ error: 'Method not allowed!' })
|
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 {
|
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();
|
const json: Faucet | ErrorResponse = await response.json();
|
||||||
if ("error" in json) {
|
if ("error" in json) {
|
||||||
return res.status(429).json(json)
|
return res.status(429).json(json)
|
||||||
|
|||||||
18
pages/api/proxy.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const { url, opts } = req.body
|
||||||
|
const r = await fetch(url, opts);
|
||||||
|
if (!r.ok) throw (r.statusText)
|
||||||
|
|
||||||
|
const data = await r.json()
|
||||||
|
return res.json(data)
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error)
|
||||||
|
return res.status(500).json({ message: "Something went wrong!" })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import React from "react";
|
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import Flex from "../../components/Flex";
|
import React from "react";
|
||||||
|
import Split from "react-split";
|
||||||
import { useSnapshot } from "valtio";
|
import { useSnapshot } from "valtio";
|
||||||
import state from "../../state";
|
import state from "../../state";
|
||||||
|
import { getSplit, saveSplit } from "../../state/actions/persistSplits";
|
||||||
|
|
||||||
const DeployEditor = dynamic(() => import("../../components/DeployEditor"), {
|
const DeployEditor = dynamic(() => import("../../components/DeployEditor"), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
@@ -17,21 +18,45 @@ const LogBox = dynamic(() => import("../../components/LogBox"), {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const Deploy = () => {
|
const Deploy = () => {
|
||||||
const snap = useSnapshot(state);
|
const { deployLogs } = useSnapshot(state);
|
||||||
return (
|
return (
|
||||||
<>
|
<Split
|
||||||
<main style={{ display: "flex", flex: 1 }}>
|
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 />
|
<DeployEditor />
|
||||||
</main>
|
</main>
|
||||||
<Flex css={{ flexDirection: "row", width: "100%" }}>
|
<Split
|
||||||
<Accounts />
|
direction="horizontal"
|
||||||
<LogBox
|
sizes={getSplit("deployHorizontal") || [50, 50]}
|
||||||
title="Deploy Log"
|
minSize={[320, 160]}
|
||||||
logs={snap.deployLogs}
|
gutterSize={4}
|
||||||
clearLog={() => (state.deployLogs = [])}
|
gutterAlign="center"
|
||||||
/>
|
style={{
|
||||||
</Flex>
|
display: "flex",
|
||||||
</>
|
flexDirection: "row",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
}}
|
||||||
|
onDragEnd={(e) => saveSplit("deployHorizontal", e)}
|
||||||
|
>
|
||||||
|
<div style={{ alignItems: "stretch", display: "flex" }}>
|
||||||
|
<Accounts />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<LogBox
|
||||||
|
title="Deploy Log"
|
||||||
|
logs={deployLogs}
|
||||||
|
clearLog={() => (state.deployLogs = [])}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Split>
|
||||||
|
</Split>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
import dynamic from "next/dynamic";
|
import { Label } from "@radix-ui/react-label";
|
||||||
import { useSnapshot } from "valtio";
|
import { Switch, SwitchThumb } from "../../components/Switch";
|
||||||
import Hotkeys from "react-hot-keys";
|
|
||||||
import { Play } from "phosphor-react";
|
|
||||||
|
|
||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
import { compileCode } from "../../state/actions";
|
import dynamic from "next/dynamic";
|
||||||
import state from "../../state";
|
import { Gear, Play } from "phosphor-react";
|
||||||
import Button from "../../components/Button";
|
import Hotkeys from "react-hot-keys";
|
||||||
|
import Split from "react-split";
|
||||||
|
import { useSnapshot } from "valtio";
|
||||||
|
import { ButtonGroup, Flex } from "../../components";
|
||||||
import Box from "../../components/Box";
|
import Box from "../../components/Box";
|
||||||
|
import Button from "../../components/Button";
|
||||||
|
import Popover from "../../components/Popover";
|
||||||
|
import state from "../../state";
|
||||||
|
import { compileCode } from "../../state/actions";
|
||||||
|
import { getSplit, saveSplit } from "../../state/actions/persistSplits";
|
||||||
|
import { styled } from "../../stitches.config";
|
||||||
|
|
||||||
const HooksEditor = dynamic(() => import("../../components/HooksEditor"), {
|
const HooksEditor = dynamic(() => import("../../components/HooksEditor"), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
@@ -17,10 +23,194 @@ const LogBox = dynamic(() => import("../../components/LogBox"), {
|
|||||||
ssr: false,
|
ssr: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const Home: NextPage = () => {
|
const OptimizationText = () => (
|
||||||
|
<span>
|
||||||
|
Specify which optimization level to use for compiling. For example -O0 means
|
||||||
|
“no optimization”: this level compiles the fastest and generates the most
|
||||||
|
debuggable code. -O2 means moderate level of optimization which enables most
|
||||||
|
optimizations. Read more about the options from{" "}
|
||||||
|
<a
|
||||||
|
className="link"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
href="https://clang.llvm.org/docs/CommandGuide/clang.html#cmdoption-o0"
|
||||||
|
>
|
||||||
|
clang documentation
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
const StyledOptimizationText = styled(OptimizationText, {
|
||||||
|
color: "$mauve12 !important",
|
||||||
|
fontSize: "200px",
|
||||||
|
"span a.link": {
|
||||||
|
color: "red",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const CompilerSettings = () => {
|
||||||
const snap = useSnapshot(state);
|
const snap = useSnapshot(state);
|
||||||
return (
|
return (
|
||||||
<>
|
<Flex css={{ minWidth: 200, flexDirection: "column", gap: "$5" }}>
|
||||||
|
<Box>
|
||||||
|
<Label
|
||||||
|
style={{
|
||||||
|
flexDirection: "row",
|
||||||
|
display: "flex",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Optimization level{" "}
|
||||||
|
<Popover
|
||||||
|
css={{
|
||||||
|
maxWidth: "240px",
|
||||||
|
lineHeight: "1.3",
|
||||||
|
a: {
|
||||||
|
color: "$purple11",
|
||||||
|
},
|
||||||
|
".dark &": {
|
||||||
|
backgroundColor: "$black !important",
|
||||||
|
|
||||||
|
".arrow": {
|
||||||
|
fill: "$colors$black",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
content={<StyledOptimizationText />}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
css={{
|
||||||
|
position: "relative",
|
||||||
|
top: "-1px",
|
||||||
|
ml: "$1",
|
||||||
|
backgroundColor: "$mauve8",
|
||||||
|
borderRadius: "$full",
|
||||||
|
cursor: "pointer",
|
||||||
|
width: "16px",
|
||||||
|
height: "16px",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
?
|
||||||
|
</Flex>
|
||||||
|
</Popover>
|
||||||
|
</Label>
|
||||||
|
<ButtonGroup css={{ mt: "$2", fontFamily: "$monospace" }}>
|
||||||
|
<Button
|
||||||
|
css={{ fontFamily: "$monospace" }}
|
||||||
|
outline={snap.compileOptions.optimizationLevel !== "-O0"}
|
||||||
|
onClick={() => (state.compileOptions.optimizationLevel = "-O0")}
|
||||||
|
>
|
||||||
|
-O0
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
css={{ fontFamily: "$monospace" }}
|
||||||
|
outline={snap.compileOptions.optimizationLevel !== "-O1"}
|
||||||
|
onClick={() => (state.compileOptions.optimizationLevel = "-O1")}
|
||||||
|
>
|
||||||
|
-O1
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
css={{ fontFamily: "$monospace" }}
|
||||||
|
outline={snap.compileOptions.optimizationLevel !== "-O2"}
|
||||||
|
onClick={() => (state.compileOptions.optimizationLevel = "-O2")}
|
||||||
|
>
|
||||||
|
-O2
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
css={{ fontFamily: "$monospace" }}
|
||||||
|
outline={snap.compileOptions.optimizationLevel !== "-O3"}
|
||||||
|
onClick={() => (state.compileOptions.optimizationLevel = "-O3")}
|
||||||
|
>
|
||||||
|
-O3
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
css={{ fontFamily: "$monospace" }}
|
||||||
|
outline={snap.compileOptions.optimizationLevel !== "-O4"}
|
||||||
|
onClick={() => (state.compileOptions.optimizationLevel = "-O4")}
|
||||||
|
>
|
||||||
|
-O4
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
css={{ fontFamily: "$monospace" }}
|
||||||
|
outline={snap.compileOptions.optimizationLevel !== "-Os"}
|
||||||
|
onClick={() => (state.compileOptions.optimizationLevel = "-Os")}
|
||||||
|
>
|
||||||
|
-Os
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
</Box>
|
||||||
|
<Box css={{ flexDirection: "column" }}>
|
||||||
|
<Label
|
||||||
|
style={{
|
||||||
|
flexDirection: "row",
|
||||||
|
display: "flex",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Clean WASM (experimental){" "}
|
||||||
|
<Popover
|
||||||
|
css={{
|
||||||
|
maxWidth: "240px",
|
||||||
|
lineHeight: "1.3",
|
||||||
|
a: {
|
||||||
|
color: "$purple11",
|
||||||
|
},
|
||||||
|
".dark &": {
|
||||||
|
backgroundColor: "$black !important",
|
||||||
|
|
||||||
|
".arrow": {
|
||||||
|
fill: "$colors$black",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
content="Cleaner removes unwanted compiler-provided exports and functions from a wasm binary to make it (more) suitable for being used as a Hook"
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
css={{
|
||||||
|
position: "relative",
|
||||||
|
top: "-1px",
|
||||||
|
mx: "$1",
|
||||||
|
backgroundColor: "$mauve8",
|
||||||
|
borderRadius: "$full",
|
||||||
|
cursor: "pointer",
|
||||||
|
width: "16px",
|
||||||
|
height: "16px",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
?
|
||||||
|
</Flex>
|
||||||
|
</Popover>
|
||||||
|
</Label>
|
||||||
|
<Switch
|
||||||
|
css={{ mt: "$2" }}
|
||||||
|
checked={snap.compileOptions.strip}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
state.compileOptions.strip = checked;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SwitchThumb />
|
||||||
|
</Switch>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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" }}>
|
<main style={{ display: "flex", flex: 1, position: "relative" }}>
|
||||||
<HooksEditor />
|
<HooksEditor />
|
||||||
{snap.files[snap.active]?.name?.split(".")?.[1].toLowerCase() ===
|
{snap.files[snap.active]?.name?.split(".")?.[1].toLowerCase() ===
|
||||||
@@ -31,12 +221,7 @@ const Home: NextPage = () => {
|
|||||||
!snap.compiling && snap.files.length && compileCode(snap.active)
|
!snap.compiling && snap.files.length && compileCode(snap.active)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Button
|
<Flex
|
||||||
variant="primary"
|
|
||||||
uppercase
|
|
||||||
disabled={!snap.files.length}
|
|
||||||
isLoading={snap.compiling}
|
|
||||||
onClick={() => compileCode(snap.active)}
|
|
||||||
css={{
|
css={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
bottom: "$4",
|
bottom: "$4",
|
||||||
@@ -44,11 +229,25 @@ const Home: NextPage = () => {
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
|
gap: "$2",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Play weight="bold" size="16px" />
|
<Button
|
||||||
Compile to Wasm
|
variant="primary"
|
||||||
</Button>
|
uppercase
|
||||||
|
disabled={!snap.files.length}
|
||||||
|
isLoading={snap.compiling}
|
||||||
|
onClick={() => compileCode(snap.active)}
|
||||||
|
>
|
||||||
|
<Play weight="bold" size="16px" />
|
||||||
|
Compile to Wasm
|
||||||
|
</Button>
|
||||||
|
<Popover content={<CompilerSettings />}>
|
||||||
|
<Button variant="primary" css={{ px: "10px" }}>
|
||||||
|
<Gear size="16px" />
|
||||||
|
</Button>
|
||||||
|
</Popover>
|
||||||
|
</Flex>
|
||||||
</Hotkeys>
|
</Hotkeys>
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
@@ -65,7 +264,7 @@ const Home: NextPage = () => {
|
|||||||
logs={snap.logs}
|
logs={snap.logs}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</Split>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,124 @@
|
|||||||
import Container from "../../components/Container";
|
import dynamic from "next/dynamic";
|
||||||
|
import Split from "react-split";
|
||||||
|
import { useSnapshot } from "valtio";
|
||||||
|
import { Box, Container, Flex, Tab, Tabs } from "../../components";
|
||||||
|
import Transaction from "../../components/Transaction";
|
||||||
|
import state from "../../state";
|
||||||
|
import { getSplit, saveSplit } from "../../state/actions/persistSplits";
|
||||||
|
import { transactionsState, modifyTransaction } from "../../state";
|
||||||
|
|
||||||
|
const DebugStream = dynamic(() => import("../../components/DebugStream"), {
|
||||||
|
ssr: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const LogBox = dynamic(() => import("../../components/LogBox"), {
|
||||||
|
ssr: false,
|
||||||
|
});
|
||||||
|
const Accounts = dynamic(() => import("../../components/Accounts"), {
|
||||||
|
ssr: false,
|
||||||
|
});
|
||||||
|
|
||||||
const Test = () => {
|
const Test = () => {
|
||||||
return <Container css={{ py: "$10" }}>This will be the test page</Container>;
|
const { transactionLogs } = useSnapshot(state);
|
||||||
|
const { transactions, activeHeader } = useSnapshot(transactionsState);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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
|
||||||
|
activeHeader={activeHeader}
|
||||||
|
// TODO make header a required field
|
||||||
|
onChangeActive={(idx, header) => {
|
||||||
|
if (header) transactionsState.activeHeader = header;
|
||||||
|
}}
|
||||||
|
keepAllAlive
|
||||||
|
forceDefaultExtension
|
||||||
|
defaultExtension=".json"
|
||||||
|
onCreateNewTab={header => modifyTransaction(header, {})}
|
||||||
|
onCloseTab={(idx, header) =>
|
||||||
|
header && modifyTransaction(header, undefined)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{transactions.map(({ header, state }) => (
|
||||||
|
<Tab key={header} header={header}>
|
||||||
|
<Transaction
|
||||||
|
state={state}
|
||||||
|
header={header}
|
||||||
|
/>
|
||||||
|
</Tab>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
</Box>
|
||||||
|
<Box css={{ width: "45%", mx: "$2", height: "100%" }}>
|
||||||
|
<Accounts card hideDeployBtn showHookStats />
|
||||||
|
</Box>
|
||||||
|
</Split>
|
||||||
|
</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>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Test;
|
export default Test;
|
||||||
|
|||||||
@@ -1,377 +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..99557cb 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,8 +41,7 @@
|
|
||||||
"Check": 67,
|
|
||||||
"Nickname": 110,
|
|
||||||
"Contract": 99,
|
|
||||||
- "NFTokenPage": 80,
|
|
||||||
- "NFTokenOffer": 55,
|
|
||||||
+ "GeneratorMap": 103,
|
|
||||||
"NegativeUNL": 78
|
|
||||||
},
|
|
||||||
"FIELDS": [
|
|
||||||
@@ -95,16 +95,6 @@
|
|
||||||
"type": "UInt16"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
- [
|
|
||||||
- "TransferFee",
|
|
||||||
- {
|
|
||||||
- "nth": 4,
|
|
||||||
- "isVLEncoded": false,
|
|
||||||
- "isSerialized": true,
|
|
||||||
- "isSigningField": true,
|
|
||||||
- "type": "UInt16"
|
|
||||||
- }
|
|
||||||
- ],
|
|
||||||
[
|
|
||||||
"Flags",
|
|
||||||
{
|
|
||||||
@@ -455,6 +445,16 @@
|
|
||||||
"type": "UInt32"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
+ [
|
|
||||||
+ "EmitGeneration",
|
|
||||||
+ {
|
|
||||||
+ "nth": 43,
|
|
||||||
+ "isVLEncoded": false,
|
|
||||||
+ "isSerialized": true,
|
|
||||||
+ "isSigningField": true,
|
|
||||||
+ "type": "UInt32"
|
|
||||||
+ }
|
|
||||||
+ ],
|
|
||||||
[
|
|
||||||
"IndexNext",
|
|
||||||
{
|
|
||||||
@@ -635,16 +635,6 @@
|
|
||||||
"type": "Hash256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
- [
|
|
||||||
- "TokenID",
|
|
||||||
- {
|
|
||||||
- "nth": 10,
|
|
||||||
- "isVLEncoded": false,
|
|
||||||
- "isSerialized": true,
|
|
||||||
- "isSigningField": true,
|
|
||||||
- "type": "Hash256"
|
|
||||||
- }
|
|
||||||
- ],
|
|
||||||
[
|
|
||||||
"BookDirectory",
|
|
||||||
{
|
|
||||||
@@ -916,7 +906,7 @@
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
- "URI",
|
|
||||||
+ "Generator",
|
|
||||||
{
|
|
||||||
"nth": 5,
|
|
||||||
"isVLEncoded": true,
|
|
||||||
@@ -1156,7 +1146,7 @@
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
- "Minter",
|
|
||||||
+ "EmitCallback",
|
|
||||||
{
|
|
||||||
"nth": 9,
|
|
||||||
"isVLEncoded": true,
|
|
||||||
@@ -1276,9 +1266,9 @@
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
- "NonFungibleToken",
|
|
||||||
+ "Signer",
|
|
||||||
{
|
|
||||||
- "nth": 12,
|
|
||||||
+ "nth": 16,
|
|
||||||
"isVLEncoded": false,
|
|
||||||
"isSerialized": true,
|
|
||||||
"isSigningField": true,
|
|
||||||
@@ -1286,9 +1276,9 @@
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
- "Signer",
|
|
||||||
+ "Majority",
|
|
||||||
{
|
|
||||||
- "nth": 16,
|
|
||||||
+ "nth": 18,
|
|
||||||
"isVLEncoded": false,
|
|
||||||
"isSerialized": true,
|
|
||||||
"isSigningField": true,
|
|
||||||
@@ -1296,9 +1286,9 @@
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
- "Majority",
|
|
||||||
+ "DisabledValidator",
|
|
||||||
{
|
|
||||||
- "nth": 18,
|
|
||||||
+ "nth": 19,
|
|
||||||
"isVLEncoded": false,
|
|
||||||
"isSerialized": true,
|
|
||||||
"isSigningField": true,
|
|
||||||
@@ -1306,9 +1296,9 @@
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
- "DisabledValidator",
|
|
||||||
+ "EmitDetails",
|
|
||||||
{
|
|
||||||
- "nth": 19,
|
|
||||||
+ "nth": 12,
|
|
||||||
"isVLEncoded": false,
|
|
||||||
"isSerialized": true,
|
|
||||||
"isSigningField": true,
|
|
||||||
@@ -1395,16 +1385,6 @@
|
|
||||||
"type": "STArray"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
- [
|
|
||||||
- "NonFungibleTokens",
|
|
||||||
- {
|
|
||||||
- "nth": 10,
|
|
||||||
- "isVLEncoded": false,
|
|
||||||
- "isSerialized": true,
|
|
||||||
- "isSigningField": true,
|
|
||||||
- "type": "STArray"
|
|
||||||
- }
|
|
||||||
- ],
|
|
||||||
[
|
|
||||||
"Majorities",
|
|
||||||
{
|
|
||||||
@@ -1535,16 +1515,6 @@
|
|
||||||
"type": "Vector256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
- [
|
|
||||||
- "TokenIDs",
|
|
||||||
- {
|
|
||||||
- "nth": 4,
|
|
||||||
- "isVLEncoded": true,
|
|
||||||
- "isSerialized": true,
|
|
||||||
- "isSigningField": true,
|
|
||||||
- "type": "Vector256"
|
|
||||||
- }
|
|
||||||
- ],
|
|
||||||
[
|
|
||||||
"Transaction",
|
|
||||||
{
|
|
||||||
@@ -1616,9 +1586,9 @@
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
- "TokenTaxon",
|
|
||||||
+ "HookStateCount",
|
|
||||||
{
|
|
||||||
- "nth": 42,
|
|
||||||
+ "nth": 40,
|
|
||||||
"isVLEncoded": false,
|
|
||||||
"isSerialized": true,
|
|
||||||
"isSigningField": true,
|
|
||||||
@@ -1626,9 +1596,9 @@
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
- "MintedTokens",
|
|
||||||
+ "HookReserveCount",
|
|
||||||
{
|
|
||||||
- "nth": 43,
|
|
||||||
+ "nth": 41,
|
|
||||||
"isVLEncoded": false,
|
|
||||||
"isSerialized": true,
|
|
||||||
"isSigningField": true,
|
|
||||||
@@ -1636,9 +1606,9 @@
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
- "BurnedTokens",
|
|
||||||
+ "HookDataMaxSize",
|
|
||||||
{
|
|
||||||
- "nth": 44,
|
|
||||||
+ "nth": 42,
|
|
||||||
"isVLEncoded": false,
|
|
||||||
"isSerialized": true,
|
|
||||||
"isSigningField": true,
|
|
||||||
@@ -1646,29 +1616,29 @@
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
- "Channel",
|
|
||||||
+ "HookOn",
|
|
||||||
{
|
|
||||||
- "nth": 22,
|
|
||||||
+ "nth": 16,
|
|
||||||
"isVLEncoded": false,
|
|
||||||
"isSerialized": true,
|
|
||||||
"isSigningField": true,
|
|
||||||
- "type": "Hash256"
|
|
||||||
+ "type": "UInt64"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
- "ConsensusHash",
|
|
||||||
+ "EmitBurden",
|
|
||||||
{
|
|
||||||
- "nth": 23,
|
|
||||||
+ "nth": 12,
|
|
||||||
"isVLEncoded": false,
|
|
||||||
"isSerialized": true,
|
|
||||||
"isSigningField": true,
|
|
||||||
- "type": "Hash256"
|
|
||||||
+ "type": "UInt64"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
- "CheckID",
|
|
||||||
+ "Channel",
|
|
||||||
{
|
|
||||||
- "nth": 24,
|
|
||||||
+ "nth": 22,
|
|
||||||
"isVLEncoded": false,
|
|
||||||
"isSerialized": true,
|
|
||||||
"isSigningField": true,
|
|
||||||
@@ -1676,9 +1646,9 @@
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
- "ValidatedHash",
|
|
||||||
+ "ConsensusHash",
|
|
||||||
{
|
|
||||||
- "nth": 25,
|
|
||||||
+ "nth": 23,
|
|
||||||
"isVLEncoded": false,
|
|
||||||
"isSerialized": true,
|
|
||||||
"isSigningField": true,
|
|
||||||
@@ -1686,9 +1656,9 @@
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
- "PreviousPageMin",
|
|
||||||
+ "CheckID",
|
|
||||||
{
|
|
||||||
- "nth": 26,
|
|
||||||
+ "nth": 24,
|
|
||||||
"isVLEncoded": false,
|
|
||||||
"isSerialized": true,
|
|
||||||
"isSigningField": true,
|
|
||||||
@@ -1696,9 +1666,9 @@
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
- "NextPageMin",
|
|
||||||
+ "ValidatedHash",
|
|
||||||
{
|
|
||||||
- "nth": 27,
|
|
||||||
+ "nth": 25,
|
|
||||||
"isVLEncoded": false,
|
|
||||||
"isSerialized": true,
|
|
||||||
"isSigningField": true,
|
|
||||||
@@ -1706,9 +1676,9 @@
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
- "BuyOffer",
|
|
||||||
+ "EmitParentTxnID",
|
|
||||||
{
|
|
||||||
- "nth": 28,
|
|
||||||
+ "nth": 10,
|
|
||||||
"isVLEncoded": false,
|
|
||||||
"isSerialized": true,
|
|
||||||
"isSigningField": true,
|
|
||||||
@@ -1716,9 +1686,9 @@
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
- "SellOffer",
|
|
||||||
+ "EmitNonce",
|
|
||||||
{
|
|
||||||
- "nth": 29,
|
|
||||||
+ "nth": 11,
|
|
||||||
"isVLEncoded": false,
|
|
||||||
"isSerialized": true,
|
|
||||||
"isSigningField": true,
|
|
||||||
@@ -1754,36 +1724,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 +1848,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,11 +1875,10 @@
|
|
||||||
"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
|
|
||||||
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;
|
||||||
|
};
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import Router from "next/router";
|
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import state, { FaucetAccountRes } from '../index';
|
import state, { FaucetAccountRes } from '../index';
|
||||||
|
|
||||||
@@ -29,14 +29,13 @@ export const names = [
|
|||||||
*/
|
*/
|
||||||
export const addFaucetAccount = async (showToast: boolean = false) => {
|
export const addFaucetAccount = async (showToast: boolean = false) => {
|
||||||
// Lets limit the number of faucet accounts to 5 for now
|
// Lets limit the number of faucet accounts to 5 for now
|
||||||
if (state.accounts.length > 4) {
|
if (state.accounts.length > 5) {
|
||||||
return toast.error("You can only have maximum 5 accounts");
|
return toast.error("You can only have maximum 6 accounts");
|
||||||
}
|
}
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
|
|
||||||
|
|
||||||
const toastId = showToast ? toast.loading("Creating account") : "";
|
const toastId = showToast ? toast.loading("Creating account") : "";
|
||||||
console.log(Router)
|
|
||||||
const res = await fetch(`${window.location.origin}/api/faucet`, {
|
const res = await fetch(`${window.location.origin}/api/faucet`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
});
|
});
|
||||||
@@ -51,14 +50,16 @@ export const addFaucetAccount = async (showToast: boolean = false) => {
|
|||||||
if (showToast) {
|
if (showToast) {
|
||||||
toast.success("New account created", { id: toastId });
|
toast.success("New account created", { id: toastId });
|
||||||
}
|
}
|
||||||
|
const currNames = state.accounts.map(acc => acc.name);
|
||||||
state.accounts.push({
|
state.accounts.push({
|
||||||
name: names[state.accounts.length],
|
name: names.filter(name => !currNames.includes(name))[0],
|
||||||
xrp: (json.xrp || 0 * 1000000).toString(),
|
xrp: (json.xrp || 0 * 1000000).toString(),
|
||||||
address: json.address,
|
address: json.address,
|
||||||
secret: json.secret,
|
secret: json.secret,
|
||||||
sequence: 1,
|
sequence: 1,
|
||||||
hooks: [],
|
hooks: [],
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
version: '2'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,11 +68,29 @@ export const addFaucetAccount = async (showToast: boolean = false) => {
|
|||||||
// fetch initial faucets
|
// fetch initial faucets
|
||||||
(async function fetchFaucets() {
|
(async function fetchFaucets() {
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
if (state.accounts.length < 2) {
|
if (state.accounts.length === 0) {
|
||||||
await addFaucetAccount();
|
await addFaucetAccount();
|
||||||
setTimeout(() => {
|
// setTimeout(() => {
|
||||||
addFaucetAccount();
|
// addFaucetAccount();
|
||||||
}, 10000);
|
// }, 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
|
// Set loading state to true
|
||||||
state.compiling = true;
|
state.compiling = true;
|
||||||
|
state.logs = []
|
||||||
try {
|
try {
|
||||||
const res = await fetch(process.env.NEXT_PUBLIC_COMPILE_API_ENDPOINT, {
|
const res = await fetch(process.env.NEXT_PUBLIC_COMPILE_API_ENDPOINT, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -34,11 +35,12 @@ export const compileCode = async (activeId: number) => {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
output: "wasm",
|
output: "wasm",
|
||||||
compress: true,
|
compress: true,
|
||||||
|
strip: state.compileOptions.strip,
|
||||||
files: [
|
files: [
|
||||||
{
|
{
|
||||||
type: "c",
|
type: "c",
|
||||||
|
options: state.compileOptions.optimizationLevel || '-O0',
|
||||||
name: state.files[activeId].name,
|
name: state.files[activeId].name,
|
||||||
options: "-O0",
|
|
||||||
src: state.files[activeId].content,
|
src: state.files[activeId].content,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -66,6 +68,7 @@ export const compileCode = async (activeId: number) => {
|
|||||||
// Decode base64 encoded wasm that is coming back from the endpoint
|
// Decode base64 encoded wasm that is coming back from the endpoint
|
||||||
const bufferData = await decodeBinary(json.output);
|
const bufferData = await decodeBinary(json.output);
|
||||||
state.files[state.active].compiledContent = ref(bufferData);
|
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
|
// Import wabt from and create human readable version of wasm file and
|
||||||
// put it into state
|
// put it into state
|
||||||
import("wabt").then((wabt) => {
|
import("wabt").then((wabt) => {
|
||||||
@@ -87,4 +90,4 @@ export const compileCode = async (activeId: number) => {
|
|||||||
});
|
});
|
||||||
state.compiling = false;
|
state.compiling = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,17 @@
|
|||||||
import state, { IFile } from '../index';
|
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) => {
|
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.files.push(emptyFile);
|
||||||
state.active = state.files.length - 1;
|
state.active = state.files.length - 1;
|
||||||
};
|
};
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
import { derive, sign } from "xrpl-accountlib";
|
|
||||||
|
|
||||||
import state, { IAccount } from "../index";
|
|
||||||
|
|
||||||
function arrayBufferToHex(arrayBuffer?: ArrayBuffer | null) {
|
|
||||||
if (!arrayBuffer) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
typeof arrayBuffer !== "object" ||
|
|
||||||
arrayBuffer === null ||
|
|
||||||
typeof arrayBuffer.byteLength !== "number"
|
|
||||||
) {
|
|
||||||
throw new TypeError("Expected input to be an ArrayBuffer");
|
|
||||||
}
|
|
||||||
|
|
||||||
var view = new Uint8Array(arrayBuffer);
|
|
||||||
var result = "";
|
|
||||||
var value;
|
|
||||||
|
|
||||||
for (var i = 0; i < view.length; i++) {
|
|
||||||
value = view[i].toString(16);
|
|
||||||
result += value.length === 1 ? "0" + value : value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* deployHook function turns the wasm binary into
|
|
||||||
* hex string, signs the transaction and deploys it to
|
|
||||||
* Hooks testnet.
|
|
||||||
*/
|
|
||||||
export const deployHook = async (account: IAccount & { name?: string }) => {
|
|
||||||
if (
|
|
||||||
!state.files ||
|
|
||||||
state.files.length === 0 ||
|
|
||||||
!state.files?.[state.active]?.compiledContent
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!state.files?.[state.active]?.compiledContent) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!state.client) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
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",
|
|
||||||
};
|
|
||||||
const keypair = derive.familySeed(account.secret);
|
|
||||||
const { signedTransaction } = sign(tx, keypair);
|
|
||||||
const currentAccount = state.accounts.find(
|
|
||||||
(acc) => acc.address === account.address
|
|
||||||
);
|
|
||||||
if (currentAccount) {
|
|
||||||
currentAccount.isLoading = true;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const submitRes = await state.client.send({
|
|
||||||
command: "submit",
|
|
||||||
tx_blob: signedTransaction,
|
|
||||||
});
|
|
||||||
if (submitRes.engine_result === "tesSUCCESS") {
|
|
||||||
state.deployLogs.push({
|
|
||||||
type: "success",
|
|
||||||
message: "Hook deployed successfully ✅",
|
|
||||||
});
|
|
||||||
state.deployLogs.push({
|
|
||||||
type: "success",
|
|
||||||
message: `[${submitRes.engine_result}] ${submitRes.engine_result_message} Validated ledger index: ${submitRes.validated_ledger_index}`,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
state.deployLogs.push({
|
|
||||||
type: "error",
|
|
||||||
message: `[${submitRes.engine_result}] ${submitRes.engine_result_message}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
state.deployLogs.push({
|
|
||||||
type: "error",
|
|
||||||
message: "Error occured while deploying",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (currentAccount) {
|
|
||||||
currentAccount.isLoading = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
282
state/actions/deployHook.tsx
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
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";
|
||||||
|
import { Link } from "../../components";
|
||||||
|
import { ref } from "valtio";
|
||||||
|
import estimateFee from "../../utils/estimateFee";
|
||||||
|
|
||||||
|
export const sha256 = 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) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
typeof arrayBuffer !== "object" ||
|
||||||
|
arrayBuffer === null ||
|
||||||
|
typeof arrayBuffer.byteLength !== "number"
|
||||||
|
) {
|
||||||
|
throw new TypeError("Expected input to be an ArrayBuffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
var view = new Uint8Array(arrayBuffer);
|
||||||
|
var result = "";
|
||||||
|
var value;
|
||||||
|
|
||||||
|
for (var i = 0; i < view.length; i++) {
|
||||||
|
value = view[i].toString(16);
|
||||||
|
result += value.length === 1 ? "0" + value : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const prepareDeployHookTx = async (
|
||||||
|
account: IAccount & { name?: string },
|
||||||
|
data: SetHookData
|
||||||
|
) => {
|
||||||
|
if (
|
||||||
|
!state.files ||
|
||||||
|
state.files.length === 0 ||
|
||||||
|
!state.files?.[state.active]?.compiledContent
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.files?.[state.active]?.compiledContent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!state.client) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const HookNamespace = (await sha256(data.HookNamespace)).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: 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",
|
||||||
|
Sequence: account.sequence,
|
||||||
|
Fee: data.Fee,
|
||||||
|
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,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
return tx;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* deployHook function turns the wasm binary into
|
||||||
|
* hex string, signs the transaction and deploys it to
|
||||||
|
* Hooks testnet.
|
||||||
|
*/
|
||||||
|
export const deployHook = async (
|
||||||
|
account: IAccount & { name?: string },
|
||||||
|
data: SetHookData
|
||||||
|
) => {
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
const tx = await prepareDeployHookTx(account, data);
|
||||||
|
if (!tx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!state.client) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const keypair = derive.familySeed(account.secret);
|
||||||
|
|
||||||
|
const { signedTransaction } = sign(tx, keypair);
|
||||||
|
const currentAccount = state.accounts.find(
|
||||||
|
(acc) => acc.address === account.address
|
||||||
|
);
|
||||||
|
if (currentAccount) {
|
||||||
|
currentAccount.isLoading = true;
|
||||||
|
}
|
||||||
|
let submitRes;
|
||||||
|
|
||||||
|
try {
|
||||||
|
submitRes = await state.client?.send({
|
||||||
|
command: "submit",
|
||||||
|
tx_blob: signedTransaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (submitRes.engine_result === "tesSUCCESS") {
|
||||||
|
state.deployLogs.push({
|
||||||
|
type: "success",
|
||||||
|
message: "Hook deployed successfully ✅",
|
||||||
|
});
|
||||||
|
state.deployLogs.push({
|
||||||
|
type: "success",
|
||||||
|
message: ref(
|
||||||
|
<>
|
||||||
|
[{submitRes.engine_result}] {submitRes.engine_result_message}{" "}
|
||||||
|
Validated ledger index:{" "}
|
||||||
|
<Link
|
||||||
|
as="a"
|
||||||
|
href={`https://${process.env.NEXT_PUBLIC_EXPLORER_URL}/${submitRes.validated_ledger_index}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{submitRes.validated_ledger_index}
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
// message: `[${submitRes.engine_result}] ${submitRes.engine_result_message} Validated ledger index: ${submitRes.validated_ledger_index}`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
state.deployLogs.push({
|
||||||
|
type: "error",
|
||||||
|
message: `[${submitRes.engine_result || submitRes.error}] ${
|
||||||
|
submitRes.engine_result_message || submitRes.error_exception
|
||||||
|
}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
state.deployLogs.push({
|
||||||
|
type: "error",
|
||||||
|
message: "Error occured while deploying",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
try {
|
||||||
|
// Update tx Fee value with network estimation
|
||||||
|
const res = await estimateFee(tx, account);
|
||||||
|
tx["Fee"] = res?.base_fee ? res?.base_fee : "1000";
|
||||||
|
} catch (err) {
|
||||||
|
// use default value what you defined earlier
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
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 state from '../index';
|
||||||
import { templateFileIds } from '../constants';
|
import { templateFileIds } from '../constants';
|
||||||
|
|
||||||
|
|
||||||
const octokit = new Octokit();
|
const octokit = new Octokit();
|
||||||
|
|
||||||
/* Fetches Gist files from Githug Gists based on
|
/* Fetches Gist files from Githug Gists based on
|
||||||
@@ -19,21 +18,43 @@ export const fetchFiles = (gistId: string) => {
|
|||||||
|
|
||||||
octokit
|
octokit
|
||||||
.request("GET /gists/{gist_id}", { gist_id: gistId })
|
.request("GET /gists/{gist_id}", { gist_id: gistId })
|
||||||
.then(res => {
|
.then(async res => {
|
||||||
if (!Object.values(templateFileIds).includes(gistId)) {
|
if (!Object.values(templateFileIds).includes(gistId)) {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
// in case of templates, fetch header file(s) and append to 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 } }) => {
|
let resHeaderJson;
|
||||||
const files = { ...res.data.files, ...headerFiles }
|
try {
|
||||||
res.data.files = files
|
const resHeader = await fetch(`${process.env.NEXT_PUBLIC_COMPILE_API_BASE_URL}/api/header-files`);
|
||||||
return res
|
if (resHeader.ok) {
|
||||||
})
|
resHeaderJson = await resHeader.json();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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) => {
|
.then((res) => {
|
||||||
if (res.data.files && Object.keys(res.data.files).length > 0) {
|
if (res.data.files && Object.keys(res.data.files).length > 0) {
|
||||||
const files = Object.keys(res.data.files).map((filename) => ({
|
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() || "",
|
language: res.data.files?.[filename]?.language?.toLowerCase() || "",
|
||||||
content: res.data.files?.[filename]?.content || "",
|
content: res.data.files?.[filename]?.content || "",
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { derive } from "xrpl-accountlib";
|
import { derive, XRPL_Account } from "xrpl-accountlib";
|
||||||
|
|
||||||
import state from '../index';
|
import state from '../index';
|
||||||
import { names } from './addFaucetAccount';
|
import { names } from './addFaucetAccount';
|
||||||
@@ -12,8 +12,18 @@ export const importAccount = (secret: string) => {
|
|||||||
if (state.accounts.find((acc) => acc.secret === secret)) {
|
if (state.accounts.find((acc) => acc.secret === secret)) {
|
||||||
return toast.error("Account already added!");
|
return toast.error("Account already added!");
|
||||||
}
|
}
|
||||||
const account = derive.familySeed(secret);
|
let account: XRPL_Account | null = null;
|
||||||
if (!account.secret.familySeed) {
|
try {
|
||||||
|
account = derive.familySeed(secret);
|
||||||
|
} catch (err: any) {
|
||||||
|
if (err?.message) {
|
||||||
|
toast.error(err.message)
|
||||||
|
} else {
|
||||||
|
toast.error('Error occured while importing account')
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!account || !account.secret.familySeed) {
|
||||||
return toast.error(`Couldn't create account!`);
|
return toast.error(`Couldn't create account!`);
|
||||||
}
|
}
|
||||||
state.accounts.push({
|
state.accounts.push({
|
||||||
@@ -24,6 +34,7 @@ export const importAccount = (secret: string) => {
|
|||||||
sequence: 1,
|
sequence: 1,
|
||||||
hooks: [],
|
hooks: [],
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
version: '2'
|
||||||
});
|
});
|
||||||
return toast.success("Account imported successfully!");
|
return toast.success("Account imported successfully!");
|
||||||
};
|
};
|
||||||
@@ -8,6 +8,7 @@ import { saveFile } from "./saveFile";
|
|||||||
import { syncToGist } from "./syncToGist";
|
import { syncToGist } from "./syncToGist";
|
||||||
import { updateEditorSettings } from "./updateEditorSettings";
|
import { updateEditorSettings } from "./updateEditorSettings";
|
||||||
import { downloadAsZip } from "./downloadAsZip";
|
import { downloadAsZip } from "./downloadAsZip";
|
||||||
|
import { sendTransaction } from "./sendTransaction";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
addFaucetAccount,
|
addFaucetAccount,
|
||||||
@@ -19,5 +20,6 @@ export {
|
|||||||
saveFile,
|
saveFile,
|
||||||
syncToGist,
|
syncToGist,
|
||||||
updateEditorSettings,
|
updateEditorSettings,
|
||||||
downloadAsZip
|
downloadAsZip,
|
||||||
|
sendTransaction
|
||||||
};
|
};
|
||||||
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
|
// Saves the current editor content to global state
|
||||||
export const saveFile = (showToast: boolean = true) => {
|
export const saveFile = (showToast: boolean = true) => {
|
||||||
const editorModels = state.editorCtx?.getModels();
|
const editorModels = state.editorCtx?.getModels();
|
||||||
|
const sought = '/' + state.files[state.active].name;
|
||||||
const currentModel = editorModels?.find((editorModel) => {
|
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) {
|
if (state.files.length > 0) {
|
||||||
state.files[state.active].content = currentModel?.getValue() || "";
|
state.files[state.active].content = currentModel?.getValue() || "";
|
||||||
@@ -13,4 +14,14 @@ export const saveFile = (showToast: boolean = true) => {
|
|||||||
if (showToast) {
|
if (showToast) {
|
||||||
toast.success("Saved successfully", { position: "bottom-center" });
|
toast.success("Saved successfully", { position: "bottom-center" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const saveAllFiles = () => {
|
||||||
|
const editorModels = state.editorCtx?.getModels();
|
||||||
|
state.files.forEach(file => {
|
||||||
|
const currentModel = editorModels?.find(model => model.uri.path.endsWith('/' + file.name))
|
||||||
|
if (currentModel) {
|
||||||
|
file.content = currentModel?.getValue() || '';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
58
state/actions/sendTransaction.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { derive, sign } from "xrpl-accountlib";
|
||||||
|
|
||||||
|
import state from '..'
|
||||||
|
import type { IAccount } from "..";
|
||||||
|
|
||||||
|
interface TransactionOptions {
|
||||||
|
TransactionType: string,
|
||||||
|
Account?: string,
|
||||||
|
Fee?: string,
|
||||||
|
Destination?: string
|
||||||
|
[index: string]: any
|
||||||
|
}
|
||||||
|
interface OtherOptions {
|
||||||
|
logPrefix?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sendTransaction = async (account: IAccount, txOptions: TransactionOptions, options?: OtherOptions) => {
|
||||||
|
if (!state.client) throw Error('XRPL client not initalized')
|
||||||
|
|
||||||
|
const { Fee = "1000", ...opts } = txOptions
|
||||||
|
const tx: TransactionOptions = {
|
||||||
|
Account: account.address,
|
||||||
|
Sequence: account.sequence, // TODO auto-fillable
|
||||||
|
Fee, // TODO auto-fillable
|
||||||
|
...opts
|
||||||
|
};
|
||||||
|
|
||||||
|
const { logPrefix = '' } = options || {}
|
||||||
|
try {
|
||||||
|
const signedAccount = derive.familySeed(account.secret);
|
||||||
|
const { signedTransaction } = sign(tx, signedAccount);
|
||||||
|
const response = await state.client.send({
|
||||||
|
command: "submit",
|
||||||
|
tx_blob: signedTransaction,
|
||||||
|
});
|
||||||
|
if (response.engine_result === "tesSUCCESS") {
|
||||||
|
state.transactionLogs.push({
|
||||||
|
type: 'success',
|
||||||
|
message: `${logPrefix}[${response.engine_result}] ${response.engine_result_message}`
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
state.transactionLogs.push({
|
||||||
|
type: "error",
|
||||||
|
message: `${logPrefix}[${response.error || response.engine_result}] ${response.error_exception || response.engine_result_message}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const currAcc = state.accounts.find(acc => acc.address === account.address);
|
||||||
|
if (currAcc && response.account_sequence_next) {
|
||||||
|
currAcc.sequence = response.account_sequence_next;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
state.transactionLogs.push({
|
||||||
|
type: "error",
|
||||||
|
message: err instanceof Error ? `${logPrefix}Error: ${err.message}` : `${logPrefix}Something went wrong, try again later`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
23
state/actions/showAlert.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { ref } from 'valtio';
|
||||||
|
import { AlertState, alertState } from "../../components/AlertDialog";
|
||||||
|
|
||||||
|
export const showAlert = (title: string, opts: Omit<Partial<AlertState>, 'title' | 'isOpen'> = {}) => {
|
||||||
|
const { body: _body, confirmPrefix: _confirmPrefix, ...rest } = opts
|
||||||
|
const body = (_body && typeof _body === 'object') ? ref(_body) : _body
|
||||||
|
const confirmPrefix = (_confirmPrefix && typeof _confirmPrefix === 'object') ? ref(_confirmPrefix) : _confirmPrefix
|
||||||
|
|
||||||
|
const nwState: AlertState = {
|
||||||
|
isOpen: true,
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
confirmPrefix,
|
||||||
|
cancelText: undefined,
|
||||||
|
confirmText: undefined,
|
||||||
|
onCancel: undefined,
|
||||||
|
onConfirm: undefined,
|
||||||
|
...rest,
|
||||||
|
}
|
||||||
|
Object.entries(nwState).forEach(([key, value]) => {
|
||||||
|
(alertState as any)[key] = value
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { Octokit } from "@octokit/core";
|
|||||||
import Router from "next/router";
|
import Router from "next/router";
|
||||||
|
|
||||||
import state from '../index';
|
import state from '../index';
|
||||||
|
import { saveAllFiles } from "./saveFile";
|
||||||
|
|
||||||
const octokit = new Octokit();
|
const octokit = new Octokit();
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ export const syncToGist = async (
|
|||||||
session?: Session | null,
|
session?: Session | null,
|
||||||
createNewGist?: boolean
|
createNewGist?: boolean
|
||||||
) => {
|
) => {
|
||||||
|
saveAllFiles();
|
||||||
let files: Record<string, { filename: string; content: string }> = {};
|
let files: Record<string, { filename: string; content: string }> = {};
|
||||||
state.gistLoading = true;
|
state.gistLoading = true;
|
||||||
|
|
||||||
|
|||||||
@@ -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 = {
|
export const templateFileIds = {
|
||||||
'starter': '1d14e51e2e02dc0a508cb0733767a914', // TODO currently same as accept
|
'starter': '1f7d2963d9e342ea092286115274f3e3',
|
||||||
'accept': '1d14e51e2e02dc0a508cb0733767a914',
|
'firewall': '70edec690f0de4dd315fad1f4f996d8c',
|
||||||
'firewall': 'bcd6d0c0fcbe52545ddb802481ff9d26',
|
'notary': '3d5677768fe8a54c4f6317e185d9ba66',
|
||||||
'notary': 'a789c75f591eeab7932fd702ed8cf9ea',
|
'carbon': 'a9fbcaf1b816b198c7fc0f62962bebf2',
|
||||||
'carbon': '43925143fa19735d8c6505c34d3a6a47',
|
'doubler': '56b86174aeb70b2b48eee962bad3e355',
|
||||||
'peggy': 'ceaf352e2a65741341033ab7ef05c448',
|
'peggy': 'd21298a37e1550b781682014762a567b',
|
||||||
'headers': '9b448e8a55fab11ef5d1274cb59f9cf3'
|
'headers': '55f639bce59a49c58c45e663776b5138'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const apiHeaderFiles = ['hookapi.h', 'sfcodes.h', 'hookmacro.h']
|
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 type monaco from "monaco-editor";
|
||||||
|
import { proxy, ref, subscribe } from "valtio";
|
||||||
|
import { devtools } from 'valtio/utils';
|
||||||
import { XrplClient } from "xrpl-client";
|
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 {
|
export interface IFile {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -9,6 +15,7 @@ export interface IFile {
|
|||||||
content: string;
|
content: string;
|
||||||
compiledContent?: ArrayBuffer | null;
|
compiledContent?: ArrayBuffer | null;
|
||||||
compiledWatContent?: string | null;
|
compiledWatContent?: string | null;
|
||||||
|
lastCompiled?: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FaucetAccountRes {
|
export interface FaucetAccountRes {
|
||||||
@@ -27,13 +34,22 @@ export interface IAccount {
|
|||||||
sequence: number;
|
sequence: number;
|
||||||
hooks: string[];
|
hooks: string[];
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
version?: string;
|
||||||
|
error?: {
|
||||||
|
message: string;
|
||||||
|
code: string;
|
||||||
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILog {
|
export interface ILog {
|
||||||
type: "error" | "warning" | "log" | "success";
|
type: "error" | "warning" | "log" | "success";
|
||||||
message: string;
|
message: string | JSX.Element;
|
||||||
|
key?: string;
|
||||||
|
jsonData?: any,
|
||||||
|
timestring?: string;
|
||||||
link?: string;
|
link?: string;
|
||||||
linkText?: string;
|
linkText?: string;
|
||||||
|
defaultCollapsed?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IState {
|
export interface IState {
|
||||||
@@ -49,25 +65,37 @@ export interface IState {
|
|||||||
compiling: boolean;
|
compiling: boolean;
|
||||||
logs: ILog[];
|
logs: ILog[];
|
||||||
deployLogs: ILog[];
|
deployLogs: ILog[];
|
||||||
|
transactionLogs: ILog[];
|
||||||
editorCtx?: typeof monaco.editor;
|
editorCtx?: typeof monaco.editor;
|
||||||
editorSettings: {
|
editorSettings: {
|
||||||
tabSize: number;
|
tabSize: number;
|
||||||
};
|
};
|
||||||
|
splits: {
|
||||||
|
[id: string]: SplitSize
|
||||||
|
};
|
||||||
client: XrplClient | null;
|
client: XrplClient | null;
|
||||||
clientStatus: "offline" | "online";
|
clientStatus: "offline" | "online";
|
||||||
mainModalOpen: boolean;
|
mainModalOpen: boolean;
|
||||||
|
mainModalShowed: boolean;
|
||||||
accounts: IAccount[];
|
accounts: IAccount[];
|
||||||
|
compileOptions: {
|
||||||
|
optimizationLevel: '-O0' | '-O1' | '-O2' | '-O3' | '-O4' | '-Os';
|
||||||
|
strip: boolean
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// let localStorageState: null | string = null;
|
// let localStorageState: null | string = null;
|
||||||
let initialState: IState = {
|
let initialState: IState = {
|
||||||
files: [],
|
files: [],
|
||||||
|
// active file index on the Develop page editor
|
||||||
active: 0,
|
active: 0,
|
||||||
|
// Active file index on the Deploy page editor
|
||||||
activeWat: 0,
|
activeWat: 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
compiling: false,
|
compiling: false,
|
||||||
logs: [],
|
logs: [],
|
||||||
deployLogs: [],
|
deployLogs: [],
|
||||||
|
transactionLogs: [],
|
||||||
editorCtx: undefined,
|
editorCtx: undefined,
|
||||||
gistId: undefined,
|
gistId: undefined,
|
||||||
gistOwner: undefined,
|
gistOwner: undefined,
|
||||||
@@ -77,14 +105,23 @@ let initialState: IState = {
|
|||||||
editorSettings: {
|
editorSettings: {
|
||||||
tabSize: 2,
|
tabSize: 2,
|
||||||
},
|
},
|
||||||
|
splits: {},
|
||||||
client: null,
|
client: null,
|
||||||
clientStatus: "offline" as "offline",
|
clientStatus: "offline" as "offline",
|
||||||
mainModalOpen: false,
|
mainModalOpen: false,
|
||||||
|
mainModalShowed: false,
|
||||||
accounts: [],
|
accounts: [],
|
||||||
|
compileOptions: {
|
||||||
|
optimizationLevel: '-O0',
|
||||||
|
strip: true
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let localStorageAccounts: string | null = null;
|
let localStorageAccounts: string | null = null;
|
||||||
let initialAccounts: IAccount[] = [];
|
let initialAccounts: IAccount[] = [];
|
||||||
|
|
||||||
|
// TODO: What exactly should we store in localStorage? editorSettings, splits, accounts?
|
||||||
|
|
||||||
// Check if there's a persited accounts in localStorage
|
// Check if there's a persited accounts in localStorage
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
try {
|
try {
|
||||||
@@ -96,6 +133,8 @@ if (typeof window !== "undefined") {
|
|||||||
if (localStorageAccounts) {
|
if (localStorageAccounts) {
|
||||||
initialAccounts = JSON.parse(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
|
// Initialize state
|
||||||
@@ -104,9 +143,8 @@ const state = proxy<IState>({
|
|||||||
accounts: initialAccounts.length > 0 ? initialAccounts : [],
|
accounts: initialAccounts.length > 0 ? initialAccounts : [],
|
||||||
logs: [],
|
logs: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize socket connection
|
// 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", () => {
|
client.on("online", () => {
|
||||||
state.client = ref(client);
|
state.client = ref(client);
|
||||||
@@ -133,4 +171,6 @@ if (typeof window !== "undefined") {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
export default state
|
export default state
|
||||||
|
|
||||||
|
export * from './transactions'
|
||||||
|
|||||||
225
state/transactions.ts
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
import { proxy } from 'valtio';
|
||||||
|
import { deepEqual } from '../utils/object';
|
||||||
|
import transactionsData from "../content/transactions.json";
|
||||||
|
import state from '.';
|
||||||
|
import { showAlert } from "../state/actions/showAlert";
|
||||||
|
import { parseJSON } from '../utils/json';
|
||||||
|
|
||||||
|
export type SelectOption = {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface TransactionState {
|
||||||
|
selectedTransaction: SelectOption | null;
|
||||||
|
selectedAccount: SelectOption | null;
|
||||||
|
selectedDestAccount: SelectOption | null;
|
||||||
|
txIsLoading: boolean;
|
||||||
|
txIsDisabled: boolean;
|
||||||
|
txFields: TxFields;
|
||||||
|
viewType: 'json' | 'ui',
|
||||||
|
editorSavedValue: null | string,
|
||||||
|
editorValue?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type TxFields = Omit<
|
||||||
|
typeof transactionsData[0],
|
||||||
|
"Account" | "Sequence" | "TransactionType"
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const defaultTransaction: TransactionState = {
|
||||||
|
selectedTransaction: null,
|
||||||
|
selectedAccount: null,
|
||||||
|
selectedDestAccount: null,
|
||||||
|
txIsLoading: false,
|
||||||
|
txIsDisabled: false,
|
||||||
|
txFields: {},
|
||||||
|
viewType: 'ui',
|
||||||
|
editorSavedValue: null
|
||||||
|
};
|
||||||
|
|
||||||
|
export const transactionsState = proxy({
|
||||||
|
transactions: [
|
||||||
|
{
|
||||||
|
header: "test1.json",
|
||||||
|
state: defaultTransaction,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
activeHeader: "test1.json"
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple transaction state changer
|
||||||
|
* @param header Unique key and tab name for the transaction tab
|
||||||
|
* @param partialTx partial transaction state, `undefined` deletes the transaction
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const modifyTransaction = (
|
||||||
|
header: string,
|
||||||
|
partialTx?: Partial<TransactionState>,
|
||||||
|
opts: { replaceState?: boolean } = {}
|
||||||
|
) => {
|
||||||
|
const tx = transactionsState.transactions.find(tx => tx.header === header);
|
||||||
|
|
||||||
|
if (partialTx === undefined) {
|
||||||
|
transactionsState.transactions = transactionsState.transactions.filter(
|
||||||
|
tx => tx.header !== header
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tx) {
|
||||||
|
const state = {
|
||||||
|
...defaultTransaction,
|
||||||
|
...partialTx,
|
||||||
|
}
|
||||||
|
transactionsState.transactions.push({
|
||||||
|
header,
|
||||||
|
state,
|
||||||
|
});
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.replaceState) {
|
||||||
|
const repTx: TransactionState = {
|
||||||
|
...defaultTransaction,
|
||||||
|
...partialTx,
|
||||||
|
}
|
||||||
|
tx.state = repTx
|
||||||
|
return repTx
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(partialTx).forEach(k => {
|
||||||
|
// Typescript mess here, but is definetly safe!
|
||||||
|
const s = tx.state as any;
|
||||||
|
const p = partialTx as any;
|
||||||
|
if (!deepEqual(s[k], p[k])) s[k] = p[k];
|
||||||
|
});
|
||||||
|
|
||||||
|
return tx.state
|
||||||
|
};
|
||||||
|
|
||||||
|
// state to tx options
|
||||||
|
export const prepareTransaction = (data: any) => {
|
||||||
|
let options = { ...data };
|
||||||
|
|
||||||
|
(Object.keys(options)).forEach(field => {
|
||||||
|
let _value = options[field];
|
||||||
|
// convert xrp
|
||||||
|
if (_value && typeof _value === "object" && _value.$type === "xrp") {
|
||||||
|
if (+_value.$value) {
|
||||||
|
options[field] = (+_value.$value * 1000000 + "") as any;
|
||||||
|
} else {
|
||||||
|
options[field] = undefined; // 👇 💀
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// handle type: `json`
|
||||||
|
if (_value && typeof _value === "object" && _value.$type === "json") {
|
||||||
|
if (typeof _value.$value === "object") {
|
||||||
|
options[field] = _value.$value as any;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
options[field] = JSON.parse(_value.$value);
|
||||||
|
} catch (error) {
|
||||||
|
const message = `Input error for json field '${field}': ${error instanceof Error ? error.message : ""
|
||||||
|
}`;
|
||||||
|
console.error(message)
|
||||||
|
options[field] = _value.$value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete unneccesary fields
|
||||||
|
if (options[field] === undefined) {
|
||||||
|
delete options[field];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
// editor value to state
|
||||||
|
export const prepareState = (value: string, txState: TransactionState) => {
|
||||||
|
const options = parseJSON(value);
|
||||||
|
if (!options) {
|
||||||
|
showAlert("Error!", {
|
||||||
|
body: "Cannot save editor with malformed transaction."
|
||||||
|
})
|
||||||
|
return
|
||||||
|
};
|
||||||
|
|
||||||
|
const { Account, TransactionType, Destination, ...rest } = options;
|
||||||
|
let tx: Partial<TransactionState> = {};
|
||||||
|
const { txFields } = txState
|
||||||
|
|
||||||
|
if (Account) {
|
||||||
|
const acc = state.accounts.find(acc => acc.address === Account);
|
||||||
|
if (acc) {
|
||||||
|
tx.selectedAccount = {
|
||||||
|
label: acc.name,
|
||||||
|
value: acc.address,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
tx.selectedAccount = {
|
||||||
|
label: Account,
|
||||||
|
value: Account,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tx.selectedAccount = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TransactionType) {
|
||||||
|
tx.selectedTransaction = {
|
||||||
|
label: TransactionType,
|
||||||
|
value: TransactionType,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
tx.selectedTransaction = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (txFields.Destination !== undefined) {
|
||||||
|
const dest = state.accounts.find(acc => acc.address === Destination);
|
||||||
|
rest.Destination = null
|
||||||
|
if (dest) {
|
||||||
|
tx.selectedDestAccount = {
|
||||||
|
label: dest.name,
|
||||||
|
value: dest.address,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (Destination) {
|
||||||
|
tx.selectedDestAccount = {
|
||||||
|
label: Destination,
|
||||||
|
value: Destination,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tx.selectedDestAccount = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(rest).forEach(field => {
|
||||||
|
const value = rest[field];
|
||||||
|
const origValue = txFields[field as keyof TxFields]
|
||||||
|
const isXrp = typeof value !== 'object' && origValue && typeof origValue === 'object' && origValue.$type === 'xrp'
|
||||||
|
if (isXrp) {
|
||||||
|
rest[field] = {
|
||||||
|
$type: "xrp",
|
||||||
|
$value: +value / 1000000, // TODO maybe use bigint?
|
||||||
|
};
|
||||||
|
} else if (typeof value === "object") {
|
||||||
|
rest[field] = {
|
||||||
|
$type: "json",
|
||||||
|
$value: value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tx.txFields = rest;
|
||||||
|
tx.editorSavedValue = null;
|
||||||
|
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
|
||||||
|
export { transactionsData }
|
||||||
@@ -1,28 +1,31 @@
|
|||||||
// stitches.config.ts
|
// stitches.config.ts
|
||||||
import type Stitches from '@stitches/react';
|
import type Stitches from "@stitches/react";
|
||||||
import { createStitches } from '@stitches/react';
|
import { createStitches } from "@stitches/react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
gray,
|
gray,
|
||||||
blue,
|
blue,
|
||||||
red,
|
crimson,
|
||||||
green,
|
grass,
|
||||||
plum,
|
|
||||||
slate,
|
slate,
|
||||||
mauve,
|
mauve,
|
||||||
pink,
|
mauveA,
|
||||||
yellow,
|
amber,
|
||||||
purple,
|
purple,
|
||||||
|
green,
|
||||||
grayDark,
|
grayDark,
|
||||||
blueDark,
|
blueDark,
|
||||||
redDark,
|
crimsonDark,
|
||||||
greenDark,
|
grassDark,
|
||||||
plumDark,
|
|
||||||
slateDark,
|
slateDark,
|
||||||
mauveDark,
|
mauveDark,
|
||||||
pinkDark,
|
mauveDarkA,
|
||||||
yellowDark,
|
amberDark,
|
||||||
purpleDark,
|
purpleDark,
|
||||||
} from '@radix-ui/colors';
|
greenDark,
|
||||||
|
red,
|
||||||
|
redDark,
|
||||||
|
} from "@radix-ui/colors";
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
styled,
|
styled,
|
||||||
@@ -38,24 +41,32 @@ export const {
|
|||||||
colors: {
|
colors: {
|
||||||
...gray,
|
...gray,
|
||||||
...blue,
|
...blue,
|
||||||
...red,
|
...crimson,
|
||||||
...green,
|
...grass,
|
||||||
...plum,
|
|
||||||
...slate,
|
...slate,
|
||||||
...mauve,
|
...mauve,
|
||||||
...pink,
|
...mauveA,
|
||||||
...yellow,
|
...amber,
|
||||||
...purple,
|
...purple,
|
||||||
|
...green,
|
||||||
|
...red,
|
||||||
|
accent: "#9D2DFF",
|
||||||
background: "$gray1",
|
background: "$gray1",
|
||||||
|
backgroundAlt: "$gray4",
|
||||||
text: "$gray12",
|
text: "$gray12",
|
||||||
|
textMuted: "$gray10",
|
||||||
primary: "$plum",
|
primary: "$plum",
|
||||||
|
error: '$red9',
|
||||||
|
warning: '$amber11',
|
||||||
|
success: "$grass11",
|
||||||
white: "white",
|
white: "white",
|
||||||
black: "black"
|
black: "black",
|
||||||
|
deep: "rgb(244, 244, 244)",
|
||||||
},
|
},
|
||||||
fonts: {
|
fonts: {
|
||||||
body: 'Work Sans, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif',
|
body: 'Work Sans, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif',
|
||||||
heading: 'Work Sans, sans-serif',
|
heading: "Work Sans, sans-serif",
|
||||||
monospace: 'Roboto Mono, monospace',
|
monospace: "Roboto Mono, monospace",
|
||||||
},
|
},
|
||||||
fontSizes: {
|
fontSizes: {
|
||||||
xs: "0.6875rem",
|
xs: "0.6875rem",
|
||||||
@@ -71,7 +82,7 @@ export const {
|
|||||||
"7xl": "4.5rem",
|
"7xl": "4.5rem",
|
||||||
"8xl": "6rem",
|
"8xl": "6rem",
|
||||||
"9xl": "8rem",
|
"9xl": "8rem",
|
||||||
default: '$md'
|
default: "$md",
|
||||||
},
|
},
|
||||||
space: {
|
space: {
|
||||||
px: "1px",
|
px: "1px",
|
||||||
@@ -107,15 +118,15 @@ export const {
|
|||||||
72: "18rem",
|
72: "18rem",
|
||||||
80: "20rem",
|
80: "20rem",
|
||||||
96: "24rem",
|
96: "24rem",
|
||||||
"widePlus": '2048px',
|
widePlus: "2048px",
|
||||||
"wide": '1536px',
|
wide: "1536px",
|
||||||
"layoutPlus": '1260px',
|
layoutPlus: "1260px",
|
||||||
"layout": '1024px',
|
layout: "1024px",
|
||||||
"copyUltra": '980px',
|
copyUltra: "980px",
|
||||||
"copyPlus": '768px',
|
copyPlus: "768px",
|
||||||
"copy": '680px',
|
copy: "680px",
|
||||||
"narrowPlus": '600px',
|
narrowPlus: "600px",
|
||||||
"narrow": '512px',
|
narrow: "512px",
|
||||||
xs: "20rem",
|
xs: "20rem",
|
||||||
sm: "24rem",
|
sm: "24rem",
|
||||||
md: "28rem",
|
md: "28rem",
|
||||||
@@ -215,62 +226,112 @@ export const {
|
|||||||
lg: "(min-width: 62em)",
|
lg: "(min-width: 62em)",
|
||||||
xl: "(min-width: 80em)",
|
xl: "(min-width: 80em)",
|
||||||
"2xl": "(min-width: 96em)",
|
"2xl": "(min-width: 96em)",
|
||||||
hover: '(any-hover: hover)',
|
hover: "(any-hover: hover)",
|
||||||
dark: '(prefers-color-scheme: dark)',
|
dark: "(prefers-color-scheme: dark)",
|
||||||
light: '(prefers-color-scheme: light)',
|
light: "(prefers-color-scheme: light)",
|
||||||
},
|
},
|
||||||
utils: {
|
utils: {
|
||||||
// Abbreviated margin properties
|
// Abbreviated margin properties
|
||||||
m: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'margin'>) => ({
|
m: (
|
||||||
|
value: Stitches.ScaleValue<"space"> | Stitches.PropertyValue<"margin">
|
||||||
|
) => ({
|
||||||
margin: value,
|
margin: value,
|
||||||
}),
|
}),
|
||||||
mt: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'marginTop'>) => ({
|
mt: (
|
||||||
|
value: Stitches.ScaleValue<"space"> | Stitches.PropertyValue<"marginTop">
|
||||||
|
) => ({
|
||||||
marginTop: value,
|
marginTop: value,
|
||||||
}),
|
}),
|
||||||
mr: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'marginRight'>) => ({
|
mr: (
|
||||||
|
value:
|
||||||
|
| Stitches.ScaleValue<"space">
|
||||||
|
| Stitches.PropertyValue<"marginRight">
|
||||||
|
) => ({
|
||||||
marginRight: value,
|
marginRight: value,
|
||||||
}),
|
}),
|
||||||
mb: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'marginBottom'>) => ({
|
mb: (
|
||||||
|
value:
|
||||||
|
| Stitches.ScaleValue<"space">
|
||||||
|
| Stitches.PropertyValue<"marginBottom">
|
||||||
|
) => ({
|
||||||
marginBottom: value,
|
marginBottom: value,
|
||||||
}),
|
}),
|
||||||
ml: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'marginLeft'>) => ({
|
ml: (
|
||||||
|
value: Stitches.ScaleValue<"space"> | Stitches.PropertyValue<"marginLeft">
|
||||||
|
) => ({
|
||||||
marginLeft: value,
|
marginLeft: value,
|
||||||
}),
|
}),
|
||||||
mx: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'marginLeft' | 'marginRight'>) => ({
|
mx: (
|
||||||
|
value:
|
||||||
|
| Stitches.ScaleValue<"space">
|
||||||
|
| Stitches.PropertyValue<"marginLeft" | "marginRight">
|
||||||
|
) => ({
|
||||||
marginLeft: value,
|
marginLeft: value,
|
||||||
marginRight: value,
|
marginRight: value,
|
||||||
}),
|
}),
|
||||||
my: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'marginTop' | 'marginBottom'>) => ({
|
my: (
|
||||||
|
value:
|
||||||
|
| Stitches.ScaleValue<"space">
|
||||||
|
| Stitches.PropertyValue<"marginTop" | "marginBottom">
|
||||||
|
) => ({
|
||||||
marginTop: value,
|
marginTop: value,
|
||||||
marginBottom: value,
|
marginBottom: value,
|
||||||
}),
|
}),
|
||||||
// Abbreviated margin properties
|
// Abbreviated margin properties
|
||||||
p: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'padding'>) => ({
|
p: (
|
||||||
|
value: Stitches.ScaleValue<"space"> | Stitches.PropertyValue<"padding">
|
||||||
|
) => ({
|
||||||
padding: value,
|
padding: value,
|
||||||
}),
|
}),
|
||||||
pt: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'paddingTop'>) => ({
|
pt: (
|
||||||
|
value: Stitches.ScaleValue<"space"> | Stitches.PropertyValue<"paddingTop">
|
||||||
|
) => ({
|
||||||
paddingTop: value,
|
paddingTop: value,
|
||||||
}),
|
}),
|
||||||
pr: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'paddingRight'>) => ({
|
pr: (
|
||||||
|
value:
|
||||||
|
| Stitches.ScaleValue<"space">
|
||||||
|
| Stitches.PropertyValue<"paddingRight">
|
||||||
|
) => ({
|
||||||
paddingRight: value,
|
paddingRight: value,
|
||||||
}),
|
}),
|
||||||
pb: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'paddingBottom'>) => ({
|
pb: (
|
||||||
|
value:
|
||||||
|
| Stitches.ScaleValue<"space">
|
||||||
|
| Stitches.PropertyValue<"paddingBottom">
|
||||||
|
) => ({
|
||||||
paddingBottom: value,
|
paddingBottom: value,
|
||||||
}),
|
}),
|
||||||
pl: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'paddingLeft'>) => ({
|
pl: (
|
||||||
|
value:
|
||||||
|
| Stitches.ScaleValue<"space">
|
||||||
|
| Stitches.PropertyValue<"paddingLeft">
|
||||||
|
) => ({
|
||||||
paddingLeft: value,
|
paddingLeft: value,
|
||||||
}),
|
}),
|
||||||
px: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'paddingLeft' | 'paddingRight'>) => ({
|
px: (
|
||||||
|
value:
|
||||||
|
| Stitches.ScaleValue<"space">
|
||||||
|
| Stitches.PropertyValue<"paddingLeft" | "paddingRight">
|
||||||
|
) => ({
|
||||||
paddingLeft: value,
|
paddingLeft: value,
|
||||||
paddingRight: value,
|
paddingRight: value,
|
||||||
}),
|
}),
|
||||||
py: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'paddingTop' | 'paddingBottom'>) => ({
|
py: (
|
||||||
|
value:
|
||||||
|
| Stitches.ScaleValue<"space">
|
||||||
|
| Stitches.PropertyValue<"paddingTop" | "paddingBottom">
|
||||||
|
) => ({
|
||||||
paddingTop: value,
|
paddingTop: value,
|
||||||
paddingBottom: value,
|
paddingBottom: value,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// A property for applying width/height together
|
// 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,
|
width: value,
|
||||||
height: value,
|
height: value,
|
||||||
}),
|
}),
|
||||||
@@ -279,45 +340,47 @@ export const {
|
|||||||
// }),
|
// }),
|
||||||
|
|
||||||
// A property to apply linear gradient
|
// A property to apply linear gradient
|
||||||
linearGradient: (value: Stitches.ScaleValue<'space'>) => ({
|
linearGradient: (value: Stitches.ScaleValue<"space">) => ({
|
||||||
backgroundImage: `linear-gradient(${value})`,
|
backgroundImage: `linear-gradient(${value})`,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// An abbreviated property for border-radius
|
// An abbreviated property for border-radius
|
||||||
br: (value: Stitches.ScaleValue<'space'>) => ({
|
br: (value: Stitches.ScaleValue<"space">) => ({
|
||||||
borderRadius: value,
|
borderRadius: value,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const darkTheme = createTheme('dark', {
|
export const darkTheme = createTheme("dark", {
|
||||||
colors: {
|
colors: {
|
||||||
...grayDark,
|
...grayDark,
|
||||||
...blueDark,
|
...blueDark,
|
||||||
...redDark,
|
...crimsonDark,
|
||||||
...greenDark,
|
...grassDark,
|
||||||
...plumDark,
|
|
||||||
...slateDark,
|
...slateDark,
|
||||||
...mauveDark,
|
...mauveDark,
|
||||||
...pinkDark,
|
...mauveDarkA,
|
||||||
...yellowDark,
|
...amberDark,
|
||||||
...purpleDark,
|
...purpleDark,
|
||||||
|
...greenDark,
|
||||||
|
...redDark,
|
||||||
|
deep: "rgb(10, 10, 10)",
|
||||||
|
// backgroundA: transparentize(0.1, grayDark.gray1),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const globalStyles = globalCss({
|
export const globalStyles = globalCss({
|
||||||
// body: { backgroundColor: '$background', color: '$text', fontFamily: 'Helvetica' },
|
// body: { backgroundColor: '$background', color: '$text', fontFamily: 'Helvetica' },
|
||||||
'html, body': {
|
"html, body": {
|
||||||
backgroundColor: '$gray1',
|
backgroundColor: "$mauve2",
|
||||||
color: '$gray12',
|
color: "$mauve12",
|
||||||
fontFamily: '$body',
|
fontFamily: "$body",
|
||||||
fontSize: '$md',
|
fontSize: "$md",
|
||||||
'-webkit-font-smoothing': 'antialiased',
|
"-webkit-font-smoothing": "antialiased",
|
||||||
'-moz-osx-font-smoothing': 'grayscale'
|
"-moz-osx-font-smoothing": "grayscale",
|
||||||
|
},
|
||||||
|
a: {
|
||||||
|
color: "inherit",
|
||||||
|
textDecoration: "none",
|
||||||
},
|
},
|
||||||
'a': {
|
|
||||||
color: 'inherit',
|
|
||||||
textDecoration: 'none'
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,8 +6,51 @@ body,
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
/* overflow-y: hidden; */
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
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,
|
"inherit": true,
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"background": "1a1d1e",
|
"background": "161618",
|
||||||
"token": ""
|
"token": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -182,10 +182,10 @@
|
|||||||
],
|
],
|
||||||
"colors": {
|
"colors": {
|
||||||
"editor.foreground": "#D0D0FF",
|
"editor.foreground": "#D0D0FF",
|
||||||
"editor.background": "#232326",
|
"editor.background": "#1C1C1F",
|
||||||
"editor.selectionBackground": "#ffffff30",
|
"editor.selectionBackground": "#ffffff30",
|
||||||
"editor.lineHighlightBackground": "#ffffff20",
|
"editor.lineHighlightBackground": "#ffffff20",
|
||||||
"editorCursor.foreground": "#7070FF",
|
"editorCursor.foreground": "#7070FF",
|
||||||
"editorWhitespace.foreground": "#BFBFBF"
|
"editorWhitespace.foreground": "#BFBFBF"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"inherit": true,
|
"inherit": true,
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"background": "FFFFFF",
|
"background": "F4F2F4",
|
||||||
"token": ""
|
"token": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -89,10 +89,10 @@
|
|||||||
],
|
],
|
||||||
"colors": {
|
"colors": {
|
||||||
"editor.foreground": "#000000",
|
"editor.foreground": "#000000",
|
||||||
"editor.background": "#f4f2f4",
|
"editor.background": "#F9F8F9",
|
||||||
"editor.selectionBackground": "#B5D5FF",
|
"editor.selectionBackground": "#B5D5FF",
|
||||||
"editor.lineHighlightBackground": "#00000012",
|
"editor.lineHighlightBackground": "#00000012",
|
||||||
"editorCursor.foreground": "#000000",
|
"editorCursor.foreground": "#000000",
|
||||||
"editorWhitespace.foreground": "#BFBFBF"
|
"editorWhitespace.foreground": "#BFBFBF"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
"**/*.tsx"
|
"**/*.tsx"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules",
|
||||||
|
"*.md"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
25
utils/estimateFee.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { derive, sign } from "xrpl-accountlib"
|
||||||
|
import state, { IAccount } from "../state"
|
||||||
|
|
||||||
|
const estimateFee = async (tx: Record<string, unknown>, account: IAccount): Promise<null | { base_fee: string, median_fee: string; minimum_fee: string; open_ledger_fee: string; }> => {
|
||||||
|
const copyTx = JSON.parse(JSON.stringify(tx))
|
||||||
|
delete copyTx['SigningPubKey']
|
||||||
|
if (!copyTx.Fee) {
|
||||||
|
copyTx.Fee = '1000'
|
||||||
|
}
|
||||||
|
const keypair = derive.familySeed(account.secret)
|
||||||
|
const { signedTransaction } = sign(copyTx, keypair);
|
||||||
|
try {
|
||||||
|
const res = await state.client?.send({ command: 'fee', tx_blob: signedTransaction })
|
||||||
|
if (res && res.drops) {
|
||||||
|
return res.drops;
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
return null
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default estimateFee
|
||||||
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
|
||||||
31
utils/json.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const parseJSON = (str?: string | null): any | undefined => {
|
||||||
|
if (!str) return undefined
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(str);
|
||||||
|
return typeof parsed === "object" ? parsed : undefined;
|
||||||
|
} catch (error) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
24
utils/object.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
export const deepEqual = (object1: any, object2: any) => {
|
||||||
|
if (!isObject(object1) || !isObject(object2)) return object1 === object2
|
||||||
|
|
||||||
|
const keys1 = Object.keys(object1);
|
||||||
|
const keys2 = Object.keys(object2);
|
||||||
|
if (keys1.length !== keys2.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const key of keys1) {
|
||||||
|
const val1 = object1[key];
|
||||||
|
const val2 = object2[key];
|
||||||
|
const areObjects = isObject(val1) && isObject(val2);
|
||||||
|
if (
|
||||||
|
areObjects && !deepEqual(val1, val2) ||
|
||||||
|
!areObjects && val1 !== val2
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
export const isObject = (object: any) => {
|
||||||
|
return object != null && typeof object === 'object';
|
||||||
|
}
|
||||||
39
utils/schema.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
export const extractSchemaProps = <O extends object>(obj: O) =>
|
||||||
|
Object.entries(obj).reduce((prev, [key, val]) => {
|
||||||
|
const typeOf = <T>(arg: T) =>
|
||||||
|
arg instanceof Array
|
||||||
|
? "array"
|
||||||
|
: arg === null
|
||||||
|
? "undefined"
|
||||||
|
: typeof arg;
|
||||||
|
|
||||||
|
const value = (typeOf(val) === "object" && '$type' in val && '$value' in val) ? val?.$value : val;
|
||||||
|
const type = typeOf(value);
|
||||||
|
|
||||||
|
let schema: any = {
|
||||||
|
title: key,
|
||||||
|
type,
|
||||||
|
default: value,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeOf(value) === 'array') {
|
||||||
|
const item = value[0] // TODO merge other item schema's into one
|
||||||
|
if (typeOf(item) !== 'object') {
|
||||||
|
schema.items = {
|
||||||
|
type: 'object',
|
||||||
|
properties: extractSchemaProps(item),
|
||||||
|
default: item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO support primitive-value arrays
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeOf(value) === "object") {
|
||||||
|
schema.properties = extractSchemaProps(value)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
[key]: schema,
|
||||||
|
};
|
||||||
|
}, {} as any);
|
||||||
|
|
||||||
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;
|
||||||
97
xrpl-hooks-docs/docs.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
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 hooksControlStringArg from "./md/hooks-control-string-arg.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 hooksReleaseDefine from "./md/hooks-release-define.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 hooksSkipHashBufLen from "./md/hooks-skip-hash-buf-len.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-control-string-arg": hooksControlStringArg,
|
||||||
|
"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-release-define": hooksReleaseDefine,
|
||||||
|
"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-skip-hash-buf-len": hooksSkipHashBufLen,
|
||||||
|
"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).
|
||||||
5
xrpl-hooks-docs/md/hooks-control-string-arg.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# hooks-control-string-arg
|
||||||
|
|
||||||
|
Functions [accept](https://xrpl-hooks.readme.io/v2.0/reference/accept) and [rollback](https://xrpl-hooks.readme.io/v2.0/reference/rollback) take an optional string buffer stored outside the hook as its result message. This is useful for debugging but takes up space.
|
||||||
|
|
||||||
|
For a release version, this check warns about constant strings passed to `accept` and `rollback`.
|
||||||