Compare commits

...

50 Commits

Author SHA1 Message Date
muzam
0def1d30a6 compiled wasm file stats 2022-02-09 19:06:29 +05:30
muzamil
3e8dbc9793 Merge pull request #91 from eqlabs/fixes
Clearing some issues
2022-02-09 18:32:47 +05:30
muzam
d735cd3833 Merge branch 'main' into fixes 2022-02-09 18:31:35 +05:30
Joni Juup
241c21782d Merge pull request #89 from eqlabs/enhancement/small-ui-fixes
new XRPL Hooks logo
clicking on the logo won't open the template dialog (retain gist path)
clicking gist under the Hook name opens the gist in a new window
documentation button open documentation in a new window
added meta tags & favicons
adjusted color schemes, removed pink (now matches design)
2022-02-09 12:42:18 +02:00
muzam
1a3f5d144c change accept template to starter 2022-02-09 14:26:19 +05:30
Joni Juup
66fb68d52e update share image 2022-02-08 18:58:59 +02:00
Joni Juup
aaeb32a576 update favicons 2022-02-08 18:29:12 +02:00
Joni Juup
78b5dcceb6 adjusting colors and themes 2022-02-08 18:07:37 +02:00
muzam
ce81c11c29 Update hooks installed info in card 2022-02-08 19:56:30 +05:30
muzam
3a98b95e3d fix firewall template link 2022-02-08 18:59:40 +05:30
muzam
8bc36655e2 disallow illegal characters in filename 2022-02-08 15:26:22 +05:30
muzam
b6ab536a60 Clear log on compile 2022-02-08 15:08:38 +05:30
Joni Juup
37a3d2b207 add share image 2022-02-07 17:10:00 +02:00
Joni Juup
cd0c5f8a0d meta tag changes 2022-02-07 16:54:31 +02:00
Vaclav Barta
8dde89fa9a Merge pull request #88 from eqlabs/feature/optimization
remove hardcoded file compilation options
2022-02-07 15:35:33 +01:00
Joni Juup
ca3d60cfb8 small ui fixes 2022-02-07 16:23:39 +02:00
Vaclav Barta
1a4d53cfbc removed hardcoded file compilation options 2022-02-07 15:03:47 +01:00
Joni Juup
94e126782b Merge pull request #65 from eqlabs/feature/panel-resize
Added panel resizing to all views.
2022-02-02 15:41:07 +02:00
Joni Juup
cc03c64f0a added a fixed height for logbox header to logbox content box height can be calculated easily 2022-02-02 15:03:31 +02:00
Joni Juup
3647aa6274 adjusted gutter sizes and highlight style 2022-02-02 12:39:08 +02:00
Joni Juup
a2a58f0ba9 light mode support 2022-02-02 12:15:58 +02:00
Joni Juup
c544a03be4 fixed log overflow, resize sizing 2022-02-02 12:12:07 +02:00
Joni Juup
9a09da88ec add panel resizing to views 2022-02-01 16:44:51 +02:00
Joni Juup
5850551906 fixed merge conflicts 2022-02-01 15:47:24 +02:00
muzam
e35e520d24 minor fix 2022-02-01 19:07:29 +05:30
muzamil
8077fc5865 Merge pull request #66 from eqlabs/feat/tabs
Transaction tabs
2022-02-01 19:02:26 +05:30
muzam
bff01b4a9f Merge branch 'main' into feat/tabs 2022-02-01 19:00:46 +05:30
muzamil
de5380d6f3 Merge pull request #67 from eqlabs/feat/debug-stream
Per account debug stream
2022-02-01 18:51:20 +05:30
muzamil
eda2a9596a Merge pull request #52 from eqlabs/transactions
Implemented Transactions!
2022-02-01 18:49:33 +05:30
muzam
195d33b1db Merge branch 'test-page' into transactions 2022-02-01 18:46:10 +05:30
muzam
4f042ef3b7 File prefixed logs 2022-02-01 18:42:19 +05:30
muzam
17c67822a9 First draft of debug stream 2022-02-01 17:05:53 +05:30
muzam
e6f613ae0b Fix tabs overflow 2022-02-01 13:53:18 +05:30
muzam
9b822cfda4 Resuable tabs component and transaction tabs 2022-01-31 20:27:49 +05:30
muzam
b5b918d877 minor changes 2022-01-31 18:55:15 +05:30
Vaclav Barta
739918647d fixed tail match 2022-01-20 10:37:24 +01:00
Vaclav Barta
1f334d6253 proposed fix for #59 2022-01-20 10:18:44 +01:00
muzam
0f15a85c45 Added additional tx type 2022-01-18 14:41:11 +05:30
muzam
0c4330e329 Support json fields and better error handling 2022-01-12 14:51:02 +05:30
muzam
a9676288ea implement reset transaction state 2022-01-11 20:20:39 +05:30
muzam
7354474c70 Implemented transactions ❤️‍🔥 2022-01-11 20:16:44 +05:30
muzam
ce5b307a8b Implement send transaction, payment works, yaay. 2022-01-10 15:21:59 +05:30
muzam
b28bcfdd0a Merge branch 'main' into test-page 2022-01-05 16:32:30 +05:30
muzam
7f06876e3e Test page UI layout 2022-01-05 16:32:07 +05:30
muzamil
fd479d8671 Merge pull request #41 from eqlabs/feat/templates
Fetch templates and header files according to selection.
2022-01-04 15:32:10 +05:30
Valtteri Karesto
938b567256 Merge pull request #46 from eqlabs/feat/patch-ripple-binary-codec
Feat/patch ripple binary codec
2021-12-23 09:08:38 +02:00
muzam
027b2c8ed4 remove console log 2021-12-21 17:07:45 +05:30
muzam
b4ca360661 Implement read-only editors for some headers. 2021-12-16 22:30:03 +05:30
muzam
ad947be0bc Only fetch extra headers on template files. 2021-12-16 18:52:16 +05:30
muzam
f739d4da34 Fetch templates and header files according to selection. 2021-12-16 18:26:58 +05:30
50 changed files with 2237 additions and 399 deletions

View File

@@ -1,18 +1,14 @@
import toast from "react-hot-toast";
import { useSnapshot } from "valtio";
import { ArrowSquareOut, Copy, Wallet, X } from "phosphor-react";
import React, { useEffect, useState } from "react";
import React, { useEffect, useState, FC } from "react";
import Dinero from "dinero.js";
import Button from "./Button";
import { addFaucetAccount, deployHook, importAccount } from "../state/actions";
import state from "../state";
import Box from "./Box";
import Container from "./Container";
import Heading from "./Heading";
import Stack from "./Stack";
import Text from "./Text";
import Flex from "./Flex";
import { Container, Heading, Stack, Text, Flex } from ".";
import {
Dialog,
DialogContent,
@@ -40,13 +36,11 @@ const AccountDialog = ({
}) => {
const snap = useSnapshot(state);
const [showSecret, setShowSecret] = useState(false);
const activeAccount = snap.accounts.find(
(account) => account.address === activeAccountAddress
);
const activeAccount = snap.accounts.find(account => account.address === activeAccountAddress);
return (
<Dialog
open={Boolean(activeAccountAddress)}
onOpenChange={(open) => {
onOpenChange={open => {
setShowSecret(false);
!open && setActiveAccountAddress(null);
}}
@@ -141,7 +135,7 @@ const AccountDialog = ({
}}
ghost
size="xs"
onClick={() => setShowSecret((curr) => !curr)}
onClick={() => setShowSecret(curr => !curr)}
>
{showSecret ? "Hide" : "Show"}
</Button>
@@ -190,7 +184,7 @@ const AccountDialog = ({
<Button
size="sm"
ghost
css={{ color: "$green11 !important", mt: "$3" }}
css={{ color: "$grass11 !important", mt: "$3" }}
>
<ArrowSquareOut size="15px" />
</Button>
@@ -205,15 +199,7 @@ const AccountDialog = ({
fontFamily: "$monospace",
}}
>
{activeAccount && activeAccount?.hooks?.length > 0
? activeAccount?.hooks
.map((i) => {
return `${i?.substring(0, 6)}...${i?.substring(
i.length - 4
)}`;
})
.join(", ")
: ""}
{activeAccount && activeAccount.hooks.length}
</Text>
</Flex>
</Flex>
@@ -229,15 +215,19 @@ const AccountDialog = ({
);
};
const Accounts = () => {
interface AccountProps {
card?: boolean;
hideDeployBtn?: boolean;
showHookStats?: boolean;
}
const Accounts: FC<AccountProps> = props => {
const snap = useSnapshot(state);
const [activeAccountAddress, setActiveAccountAddress] = useState<
string | null
>(null);
const [activeAccountAddress, setActiveAccountAddress] = useState<string | null>(null);
useEffect(() => {
const fetchAccInfo = async () => {
if (snap.clientStatus === "online") {
const requests = snap.accounts.map((acc) =>
const requests = snap.accounts.map(acc =>
snap.client?.send({
id: acc.address,
command: "account_info",
@@ -249,15 +239,13 @@ const Accounts = () => {
const address = res?.account_data?.Account as string;
const balance = res?.account_data?.Balance as string;
const sequence = res?.account_data?.Sequence as number;
const accountToUpdate = state.accounts.find(
(acc) => acc.address === address
);
const accountToUpdate = state.accounts.find(acc => acc.address === address);
if (accountToUpdate) {
accountToUpdate.xrp = balance;
accountToUpdate.sequence = sequence;
}
});
const objectRequests = snap.accounts.map((acc) => {
const objectRequests = snap.accounts.map(acc => {
return snap.client?.send({
id: `${acc.address}-hooks`,
command: "account_objects",
@@ -267,9 +255,7 @@ const Accounts = () => {
const objectResponses = await Promise.all(objectRequests);
objectResponses.forEach((res: any) => {
const address = res?.account as string;
const accountToUpdate = state.accounts.find(
(acc) => acc.address === address
);
const accountToUpdate = state.accounts.find(acc => acc.address === address);
if (accountToUpdate) {
accountToUpdate.hooks = res.account_objects
.filter((ac: any) => ac?.LedgerEntryType === "Hook")
@@ -299,16 +285,21 @@ const Accounts = () => {
as="div"
css={{
display: "flex",
borderTop: "1px solid $mauve6",
background: "$mauve1",
backgroundColor: props.card ? "$deep" : "$mauve1",
position: "relative",
width: "50%",
flexShrink: 0,
borderRight: "1px solid $mauve6",
flex: "1",
height: "100%",
border: "1px solid $mauve6",
borderRadius: props.card ? "$md" : undefined,
}}
>
<Container css={{ px: 0, flexShrink: 1 }}>
<Flex css={{ py: "$3" }}>
<Container css={{ p: 0, flexShrink: 1, height: "100%" }}>
<Flex
css={{
py: "$3",
borderBottom: props.card ? "1px solid $mauve6" : undefined,
}}
>
<Heading
as="h3"
css={{
@@ -326,79 +317,99 @@ const Accounts = () => {
<Wallet size="15px" /> <Text css={{ lineHeight: 1 }}>Accounts</Text>
</Heading>
<Flex css={{ ml: "auto", gap: "$3", marginRight: "$3" }}>
<Button ghost size="xs" onClick={() => addFaucetAccount(true)}>
<Button ghost size="sm" onClick={() => addFaucetAccount(true)}>
Create
</Button>
<ImportAccountDialog />
</Flex>
</Flex>
<Box
as="div"
<Stack
css={{
display: "flex",
flexDirection: "column",
width: "100%",
height: "160px",
fontSize: "13px",
wordWrap: "break-word",
fontWeight: "$body",
fontFamily: "$monospace",
gap: 0,
height: "calc(100% - 52px)",
flexWrap: "nowrap",
overflowY: "auto",
wordWrap: "break-word",
}}
>
<Stack css={{ flexDirection: "column", gap: 0 }}>
{snap.accounts.map((account) => (
<Flex
key={account.address + account.name}
onClick={() => setActiveAccountAddress(account.address)}
css={{
gap: "$3",
p: "$2 $3",
justifyContent: "center",
cursor: "pointer",
"@hover": {
"&:hover": {
background: "$mauve3",
},
{snap.accounts.map(account => (
<Flex
column
key={account.address + account.name}
onClick={() => setActiveAccountAddress(account.address)}
css={{
px: "$3",
py: props.card ? "$3" : "$2",
cursor: "pointer",
borderBottom: props.card ? "1px solid $mauve6" : undefined,
"@hover": {
"&:hover": {
background: "$backgroundAlt",
},
},
}}
>
<Flex
row
css={{
justifyContent: "space-between",
}}
>
<Text>{account.name} </Text>
<Text css={{ color: "$mauve9" }}>
{account.address} (
{Dinero({
amount: Number(account?.xrp || "0"),
precision: 6,
})
.toUnit()
.toLocaleString(undefined, {
style: "currency",
currency: "XRP",
currencyDisplay: "name",
})}
)
</Text>
<Button
css={{ ml: "auto" }}
size="xs"
uppercase
isLoading={account.isLoading}
disabled={
account.isLoading ||
!snap.files.filter((file) => file.compiledWatContent).length
}
variant="secondary"
onClick={(e) => {
e.stopPropagation();
deployHook(account);
}}
>
Deploy
</Button>
<Box>
<Text>{account.name} </Text>
<Text
css={{
color: "$textMuted",
wordBreak: "break-word",
}}
>
{account.address} (
{Dinero({
amount: Number(account?.xrp || "0"),
precision: 6,
})
.toUnit()
.toLocaleString(undefined, {
style: "currency",
currency: "XRP",
currencyDisplay: "name",
})}
)
</Text>
</Box>
{!props.hideDeployBtn && (
<Button
css={{ ml: "auto" }}
size="xs"
uppercase
isLoading={account.isLoading}
disabled={
account.isLoading ||
!snap.files.filter(file => file.compiledWatContent).length
}
variant="secondary"
onClick={e => {
e.stopPropagation();
deployHook(account);
}}
>
Deploy
</Button>
)}
</Flex>
))}
</Stack>
</Box>
{props.showHookStats && (
<Text muted small css={{ mt: "$2" }}>
{account.hooks.length} hook{account.hooks.length === 1 ? "" : "s"} installed
</Text>
)}
</Flex>
))}
</Stack>
</Container>
<AccountDialog
activeAccountAddress={activeAccountAddress}
@@ -413,7 +424,7 @@ const ImportAccountDialog = () => {
return (
<Dialog>
<DialogTrigger asChild>
<Button ghost size="xs">
<Button ghost size="sm">
Import
</Button>
</DialogTrigger>
@@ -425,7 +436,7 @@ const ImportAccountDialog = () => {
name="secret"
type="password"
value={value}
onChange={(e) => setValue(e.target.value)}
onChange={e => setValue(e.target.value)}
/>
</DialogDescription>

View File

@@ -66,6 +66,12 @@ export const StyledButton = styled("button", {
},
},
variant: {
link: {
textDecoration: "underline",
fontSize: "inherit",
color: "$textMuted",
textUnderlineOffset: "2px",
},
default: {
backgroundColor: "$mauve12",
boxShadow: "inset 0 0 0 1px $colors$mauve12",
@@ -81,8 +87,7 @@ export const StyledButton = styled("button", {
boxShadow: "inset 0 0 0 1px $colors$mauve11",
},
"&:focus": {
boxShadow:
"inset 0 0 0 1px $colors$mauve12, inset 0 0 0 2px $colors$mauve12",
boxShadow: "inset 0 0 0 1px $colors$mauve12, inset 0 0 0 2px $colors$mauve12",
},
'&[data-radix-popover-trigger][data-state="open"], &[data-radix-dropdown-menu-trigger][data-state="open"]':
{
@@ -91,26 +96,26 @@ export const StyledButton = styled("button", {
},
},
primary: {
backgroundColor: `$pink9`,
boxShadow: "inset 0 0 0 1px $colors$pink9",
backgroundColor: `$accent`,
boxShadow: "inset 0 0 0 1px $colors$purple9",
color: "$white",
"@hover": {
"&:hover": {
backgroundColor: "$pink10",
boxShadow: "inset 0 0 0 1px $colors$pink11",
backgroundColor: "$purple10",
boxShadow: "inset 0 0 0 1px $colors$purple11",
},
},
"&:active": {
backgroundColor: "$pink8",
boxShadow: "inset 0 0 0 1px $colors$pink8",
backgroundColor: "$purple8",
boxShadow: "inset 0 0 0 1px $colors$purple8",
},
"&:focus": {
boxShadow: "inset 0 0 0 2px $colors$pink12",
boxShadow: "inset 0 0 0 2px $colors$purple12",
},
'&[data-radix-popover-trigger][data-state="open"], &[data-radix-dropdown-menu-trigger][data-state="open"]':
{
backgroundColor: "$mauve4",
boxShadow: "inset 0 0 0 1px $colors$pink8",
boxShadow: "inset 0 0 0 1px $colors$purple8",
},
},
secondary: {
@@ -137,7 +142,11 @@ export const StyledButton = styled("button", {
},
},
},
muted: {
true: {
color: "$textMuted",
},
},
outline: {
true: {
backgroundColor: "transparent",
@@ -227,21 +236,16 @@ export const StyledButton = styled("button", {
},
});
const CustomButton: React.FC<
React.ComponentProps<typeof StyledButton> & { as?: string }
> = React.forwardRef(({ children, as = "button", ...rest }, ref) => (
// @ts-expect-error
<StyledButton {...rest} ref={ref} as={as}>
<Flex
as="span"
css={{ gap: "$2", alignItems: "center" }}
className="button-content"
>
{children}
</Flex>
{rest.isLoading && <Spinner css={{ position: "absolute" }} />}
</StyledButton>
));
const CustomButton: React.FC<React.ComponentProps<typeof StyledButton> & { as?: string }> =
React.forwardRef(({ children, as = "button", ...rest }, ref) => (
// @ts-expect-error
<StyledButton {...rest} ref={ref} as={as}>
<Flex as="span" css={{ gap: "$2", alignItems: "center" }} className="button-content">
{children}
</Flex>
{rest.isLoading && <Spinner css={{ position: "absolute" }} />}
</StyledButton>
));
CustomButton.displayName = "CustomButton";

View File

@@ -0,0 +1,86 @@
import { useEffect, useState } from "react";
import { useSnapshot } from "valtio";
import { Select } from ".";
import state from "../state";
import LogBox from "./LogBox";
import Text from "./Text";
const DebugStream = () => {
const snap = useSnapshot(state);
const accountOptions = snap.accounts.map(acc => ({
label: acc.name,
value: acc.address,
}));
const [selectedAccount, setSelectedAccount] = useState<typeof accountOptions[0] | null>(null);
const renderNav = () => (
<>
<Text css={{ mx: "$2", fontSize: "inherit" }}>Account: </Text>
<Select
instanceId="debugStreamAccount"
placeholder="Select account"
options={accountOptions}
hideSelectedOptions
value={selectedAccount}
onChange={acc => setSelectedAccount(acc as any)}
css={{ width: "30%" }}
/>
</>
);
useEffect(() => {
const account = selectedAccount?.value;
if (!account) {
return;
}
const socket = new WebSocket(`wss://hooks-testnet-debugstream.xrpl-labs.com/${account}`);
const onOpen = () => {
state.debugLogs = [];
state.debugLogs.push({
type: "success",
message: `Debug stream opened for account ${account}`,
});
};
const onError = () => {
state.debugLogs.push({
type: "error",
message: "Something went wrong in establishing connection!",
});
setSelectedAccount(null);
};
const onMessage = (event: any) => {
if (!event.data) return;
state.debugLogs.push({
type: "log",
message: event.data,
});
};
socket.addEventListener("open", onOpen);
socket.addEventListener("close", onError);
socket.addEventListener("error", onError);
socket.addEventListener("message", onMessage);
return () => {
socket.removeEventListener("open", onOpen);
socket.removeEventListener("close", onError);
socket.removeEventListener("message", onMessage);
socket.close();
};
}, [selectedAccount]);
return (
<LogBox
enhanced
renderNav={renderNav}
title="Debug stream"
logs={snap.debugLogs}
clearLog={() => (state.debugLogs = [])}
/>
);
};
export default DebugStream;

View File

@@ -1,10 +1,12 @@
import React, { useRef } from "react";
import React, { useRef, useState } from "react";
import { useSnapshot, ref } from "valtio";
import Editor, { loader } from "@monaco-editor/react";
import type monaco from "monaco-editor";
import { useTheme } from "next-themes";
import { useRouter } from "next/router";
import NextLink from "next/link";
import ReactTimeAgo from "react-time-ago";
import filesize from "filesize";
import Box from "./Box";
import Container from "./Container";
@@ -13,8 +15,7 @@ import light from "../theme/editor/xcode_default.json";
import state from "../state";
import EditorNavigation from "./EditorNavigation";
import Text from "./Text";
import Link from "./Link";
import { Button, Text, Link, Flex } from ".";
loader.config({
paths: {
@@ -22,11 +23,65 @@ loader.config({
},
});
const FILESIZE_BREAKPOINTS: [number, number] = [2 * 1024, 5 * 1024];
const DeployEditor = () => {
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>();
const snap = useSnapshot(state);
const router = useRouter();
const { theme } = useTheme();
const [showContent, setShowContent] = useState(false);
const activeFile = snap.files[snap.active];
const compiledSize = activeFile?.compiledContent?.byteLength || 0;
const color =
compiledSize > FILESIZE_BREAKPOINTS[1]
? "$error"
: compiledSize > FILESIZE_BREAKPOINTS[0]
? "$warning"
: "$success";
const CompiledStatView = activeFile && (
<Flex
column
align="center"
css={{
fontSize: "$sm",
fontFamily: "$monospace",
textAlign: "center",
}}
>
<Flex row align="center">
<Text css={{ mr: "$1" }}>Compiled {activeFile.name.split(".")[0] + ".wasm"}</Text>
{activeFile?.lastCompiled && <ReactTimeAgo date={activeFile.lastCompiled} locale="en-US" />}
{activeFile.compiledContent?.byteLength && (
<Text css={{ ml: "$2", color }}>({filesize(activeFile.compiledContent.byteLength)})</Text>
)}
</Flex>
<Button variant="link" onClick={() => setShowContent(true)}>
View as WAT-file
</Button>
</Flex>
);
const NoContentView = !snap.loading && router.isReady && (
<Text
css={{
mt: "-60px",
fontSize: "$sm",
fontFamily: "$monospace",
maxWidth: "300px",
textAlign: "center",
}}
>
{`You haven't compiled any files yet, compile files on `}
<NextLink shallow href={`/develop/${router.query.slug}`} passHref>
<Link as="a">develop view</Link>
</NextLink>
</Text>
);
const isContent =
snap.files?.filter(file => file.compiledWatContent).length > 0 && router.isReady;
return (
<Box
css={{
@@ -34,65 +89,50 @@ const DeployEditor = () => {
display: "flex",
position: "relative",
flexDirection: "column",
backgroundColor: "$mauve3",
backgroundColor: "$mauve2",
width: "100%",
}}
>
<EditorNavigation showWat />
{snap.files?.filter((file) => file.compiledWatContent).length > 0 &&
router.isReady ? (
<Editor
className="hooks-editor"
// keepCurrentModel
defaultLanguage={snap.files?.[snap.active]?.language}
language={snap.files?.[snap.active]?.language}
path={`file://tmp/c/${snap.files?.[snap.active]?.name}.wat`}
value={snap.files?.[snap.active]?.compiledWatContent || ""}
beforeMount={(monaco) => {
if (!state.editorCtx) {
state.editorCtx = ref(monaco.editor);
// @ts-expect-error
monaco.editor.defineTheme("dark", dark);
// @ts-expect-error
monaco.editor.defineTheme("light", light);
}
}}
onMount={(editor, monaco) => {
editorRef.current = editor;
editor.updateOptions({
glyphMargin: true,
readOnly: true,
});
}}
theme={theme === "dark" ? "dark" : "light"}
/>
) : (
<Container
css={{
display: "flex",
flex: 1,
justifyContent: "center",
alignItems: "center",
}}
>
{!snap.loading && router.isReady && (
<Text
css={{
mt: "-60px",
fontSize: "14px",
fontFamily: "$monospace",
maxWidth: "300px",
textAlign: "center",
}}
>
{`You haven't compiled any files yet, compile files on `}
<NextLink shallow href={`/develop/${router.query.slug}`} passHref>
<Link as="a">develop view</Link>
</NextLink>
</Text>
)}
</Container>
)}
<Container
css={{
display: "flex",
flex: 1,
justifyContent: "center",
alignItems: "center",
}}
>
{!isContent ? (
NoContentView
) : !showContent ? (
CompiledStatView
) : (
<Editor
className="hooks-editor"
defaultLanguage={snap.files?.[snap.active]?.language}
language={snap.files?.[snap.active]?.language}
path={`file://tmp/c/${snap.files?.[snap.active]?.name}.wat`}
value={snap.files?.[snap.active]?.compiledWatContent || ""}
beforeMount={monaco => {
if (!state.editorCtx) {
state.editorCtx = ref(monaco.editor);
// @ts-expect-error
monaco.editor.defineTheme("dark", dark);
// @ts-expect-error
monaco.editor.defineTheme("light", light);
}
}}
onMount={(editor, monaco) => {
editorRef.current = editor;
editor.updateOptions({
glyphMargin: true,
readOnly: true,
});
}}
theme={theme === "dark" ? "dark" : "light"}
/>
)}
</Container>
</Box>
);
};

View File

@@ -71,7 +71,7 @@ const itemStyles = {
},
"&:focus": {
backgroundColor: "$pink9",
backgroundColor: "$purple9",
color: "$white",
},
};
@@ -85,8 +85,8 @@ const StyledRadioItem = styled(DropdownMenuPrimitive.RadioItem, {
});
const StyledTriggerItem = styled(DropdownMenuPrimitive.TriggerItem, {
'&[data-state="open"]': {
backgroundColor: "$pink9",
color: "$pink9",
backgroundColor: "$purple9",
color: "$purple9",
},
...itemStyles,
});

View File

@@ -26,7 +26,12 @@ import NewWindow from "react-new-window";
import { signOut, useSession } from "next-auth/react";
import { useSnapshot } from "valtio";
import { createNewFile, syncToGist, updateEditorSettings, downloadAsZip } from "../state/actions";
import {
createNewFile,
syncToGist,
updateEditorSettings,
downloadAsZip,
} from "../state/actions";
import state from "../state";
import Box from "./Box";
import Button from "./Button";
@@ -57,7 +62,7 @@ import { styled } from "../stitches.config";
const DEFAULT_EXTENSION = ".c";
const ErrorText = styled(Text, {
color: "$red9",
color: "$error",
mt: "$1",
display: "block",
});
@@ -85,9 +90,16 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
const validateFilename = useCallback(
(filename: string): { error: string | null } => {
// check if filename already exists
if (snap.files.find(file => file.name === filename)) {
return { error: "Filename already exists." };
}
// check for illegal characters
const ILLEGAL_REGEX = /[/]/gi;
if (filename.match(ILLEGAL_REGEX)) {
return { error: "Filename contains illegal characters" };
}
// More checks in future
return { error: null };
},
@@ -95,7 +107,9 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
);
const handleConfirm = useCallback(() => {
// add default extension in case omitted
let _filename = filename.includes(".") ? filename : filename + DEFAULT_EXTENSION;
let _filename = filename.includes(".")
? filename
: filename + DEFAULT_EXTENSION;
const chk = validateFilename(_filename);
if (chk.error) {
setNewfileError(`Error: ${chk.error}`);
@@ -139,7 +153,9 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
return (
<Button
size="sm"
outline={showWat ? snap.activeWat !== index : snap.active !== index}
outline={
showWat ? snap.activeWat !== index : snap.active !== index
}
onClick={() => (state.active = index)}
key={file.name + index}
css={{
@@ -174,7 +190,8 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
// If deleted file is behind active tab
// we keep the current state otherwise
// select previous file on the list
state.active = index > snap.active ? snap.active : snap.active - 1;
state.active =
index > snap.active ? snap.active : snap.active - 1;
}}
>
<X size="9px" weight="bold" />
@@ -184,10 +201,18 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
);
})}
{!showWat && (
<Dialog open={isNewfileDialogOpen} onOpenChange={setIsNewfileDialogOpen}>
<Dialog
open={isNewfileDialogOpen}
onOpenChange={setIsNewfileDialogOpen}
>
<DialogTrigger asChild>
<Button ghost size="sm" css={{ alignItems: "center", px: "$2", mr: "$3" }}>
<Plus size="16px" /> {snap.files.length === 0 && "Add new file"}
<Button
ghost
size="sm"
css={{ alignItems: "center", px: "$2", mr: "$3" }}
>
<Plus size="16px" />{" "}
{snap.files.length === 0 && "Add new file"}
</Button>
</DialogTrigger>
<DialogContent>
@@ -196,8 +221,8 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
<label>Filename</label>
<Input
value={filename}
onChange={e => setFilename(e.target.value)}
onKeyPress={e => {
onChange={(e) => setFilename(e.target.value)}
onKeyPress={(e) => {
if (e.key === "Enter") {
handleConfirm();
}
@@ -216,10 +241,7 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
<DialogClose asChild>
<Button outline>Cancel</Button>
</DialogClose>
<Button
variant="primary"
onClick={handleConfirm}
>
<Button variant="primary" onClick={handleConfirm}>
Create file
</Button>
</Flex>
@@ -237,11 +259,13 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
<Flex
css={{
py: "$3",
backgroundColor: "$mauve3",
backgroundColor: "$mauve2",
zIndex: 1,
}}
>
<Container css={{ width: "unset", display: "flex", alignItems: "center" }}>
<Container
css={{ width: "unset", display: "flex", alignItems: "center" }}
>
{status === "authenticated" ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
@@ -274,10 +298,15 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem disabled onClick={() => signOut()}>
<User size="16px" /> {session?.user?.username} ({session?.user.name})
<User size="16px" /> {session?.user?.username} (
{session?.user.name})
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => window.open(`http://gist.github.com/${session?.user.username}`)}
onClick={() =>
window.open(
`http://gist.github.com/${session?.user.username}`
)
}
>
<ArrowSquareOut size="16px" />
Go to your Gist
@@ -291,7 +320,12 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
</DropdownMenuContent>
</DropdownMenu>
) : (
<Button outline size="sm" css={{ mr: "$3" }} onClick={() => setPopUp(true)}>
<Button
outline
size="sm"
css={{ mr: "$3" }}
onClick={() => setPopUp(true)}
>
<GithubLogo size="16px" /> Login
</Button>
)}
@@ -330,7 +364,13 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
},
}}
>
<Button isLoading={snap.zipLoading} onClick={downloadAsZip} outline size="sm" css={{ alignItems: "center" }}>
<Button
isLoading={snap.zipLoading}
onClick={downloadAsZip}
outline
size="sm"
css={{ alignItems: "center" }}
>
<DownloadSimple size="16px" />
</Button>
<Button
@@ -338,7 +378,9 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
size="sm"
css={{ alignItems: "center" }}
onClick={() => {
navigator.clipboard.writeText(`${window.location.origin}/develop/${snap.gistId}`);
navigator.clipboard.writeText(
`${window.location.origin}/develop/${snap.gistId}`
);
toast.success("Copied share link to clipboard!");
}}
>
@@ -368,7 +410,10 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem disabled={snap.zipLoading} onClick={downloadAsZip}>
<DropdownMenuItem
disabled={snap.zipLoading}
onClick={downloadAsZip}
>
<DownloadSimple size="16px" /> Download as ZIP
</DropdownMenuItem>
<DropdownMenuItem
@@ -383,7 +428,9 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
Copy share link to clipboard
</DropdownMenuItem>
<DropdownMenuItem
disabled={session?.user.username !== snap.gistOwner || !snap.gistId}
disabled={
session?.user.username !== snap.gistOwner || !snap.gistId
}
onClick={() => {
syncToGist(session);
}}
@@ -409,15 +456,21 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
</DropdownMenu>
</Stack>
{popup && !session ? <NewWindow center="parent" url="/sign-in" /> : null}
{popup && !session ? (
<NewWindow center="parent" url="/sign-in" />
) : null}
</Container>
</Flex>
<AlertDialog open={createNewAlertOpen} onOpenChange={value => setCreateNewAlertOpen(value)}>
<AlertDialog
open={createNewAlertOpen}
onOpenChange={(value) => setCreateNewAlertOpen(value)}
>
<AlertDialogContent>
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
<AlertDialogDescription>
This action will create new <strong>public</strong> Github Gist from your current saved
files. You can delete gist anytime from your GitHub Gists page.
This action will create new <strong>public</strong> Github Gist from
your current saved files. You can delete gist anytime from your
GitHub Gists page.
</AlertDialogDescription>
<Flex css={{ justifyContent: "flex-end", gap: "$3" }}>
<AlertDialogCancel asChild>
@@ -451,8 +504,8 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
type="number"
min="1"
value={editorSettings.tabSize}
onChange={e =>
setEditorSettings(curr => ({
onChange={(e) =>
setEditorSettings((curr) => ({
...curr,
tabSize: Number(e.target.value),
}))
@@ -462,12 +515,18 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
<Flex css={{ marginTop: 25, justifyContent: "flex-end", gap: "$3" }}>
<DialogClose asChild>
<Button outline onClick={() => updateEditorSettings(editorSettings)}>
<Button
outline
onClick={() => updateEditorSettings(editorSettings)}
>
Cancel
</Button>
</DialogClose>
<DialogClose asChild>
<Button variant="primary" onClick={() => updateEditorSettings(editorSettings)}>
<Button
variant="primary"
onClick={() => updateEditorSettings(editorSettings)}
>
Save changes
</Button>
</DialogClose>

View File

@@ -3,6 +3,51 @@ import Box from "./Box";
const Flex = styled(Box, {
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;

View File

@@ -11,6 +11,7 @@ import Container from "./Container";
import dark from "../theme/editor/amy.json";
import light from "../theme/editor/xcode_default.json";
import { saveFile } from "../state/actions";
import { apiHeaderFiles } from "../state/constants";
import state from "../state";
import EditorNavigation from "./EditorNavigation";
@@ -26,12 +27,26 @@ loader.config({
},
});
const validateWritability = (editor: monaco.editor.IStandaloneCodeEditor) => {
const currPath = editor.getModel()?.uri.path;
if (apiHeaderFiles.find((h) => currPath?.endsWith(h))) {
editor.updateOptions({ readOnly: true });
} else {
editor.updateOptions({ readOnly: false });
}
};
const HooksEditor = () => {
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>();
const subscriptionRef = useRef<ReconnectingWebSocket | null>(null);
const snap = useSnapshot(state);
const router = useRouter();
const { theme } = useTheme();
useEffect(() => {
if (editorRef.current) validateWritability(editorRef.current);
}, [snap.active]);
useEffect(() => {
return () => {
subscriptionRef?.current?.close();
@@ -45,7 +60,7 @@ const HooksEditor = () => {
display: "flex",
position: "relative",
flexDirection: "column",
backgroundColor: "$mauve3",
backgroundColor: "$mauve2",
width: "100%",
}}
>
@@ -56,7 +71,7 @@ const HooksEditor = () => {
keepCurrentModel
defaultLanguage={snap.files?.[snap.active]?.language}
language={snap.files?.[snap.active]?.language}
path={`file://work/c/${snap.files?.[snap.active]?.name}`}
path={`file:///work/c/${snap.files?.[snap.active]?.name}`}
defaultValue={snap.files?.[snap.active]?.content}
beforeMount={(monaco) => {
if (!snap.editorCtx) {
@@ -64,7 +79,7 @@ const HooksEditor = () => {
monaco.editor.createModel(
file.content,
file.language,
monaco.Uri.parse(`file://work/c/${file.name}`)
monaco.Uri.parse(`file:///work/c/${file.name}`)
)
);
}
@@ -130,6 +145,7 @@ const HooksEditor = () => {
saveFile();
}
);
validateWritability(editor);
}}
theme={theme === "dark" ? "dark" : "light"}
/>

View File

@@ -110,20 +110,24 @@ export const Input = styled("input", {
backgroundColor: "transparent",
},
},
deep: {
backgroundColor: "$deep",
boxShadow: "none",
},
},
state: {
invalid: {
boxShadow: "inset 0 0 0 1px $colors$red7",
boxShadow: "inset 0 0 0 1px $colors$crimson7",
"&:focus": {
boxShadow:
"inset 0px 0px 0px 1px $colors$red8, 0px 0px 0px 1px $colors$red8",
"inset 0px 0px 0px 1px $colors$crimson8, 0px 0px 0px 1px $colors$crimson8",
},
},
valid: {
boxShadow: "inset 0 0 0 1px $colors$green7",
boxShadow: "inset 0 0 0 1px $colors$grass7",
"&:focus": {
boxShadow:
"inset 0px 0px 0px 1px $colors$green8, 0px 0px 0px 1px $colors$green8",
"inset 0px 0px 0px 1px $colors$grass8, 0px 0px 0px 1px $colors$grass8",
},
},
},

View File

@@ -1,4 +1,4 @@
import React, { useRef, useLayoutEffect } from "react";
import React, { useRef, useLayoutEffect, ReactNode } from "react";
import { Notepad, Prohibit } from "phosphor-react";
import useStayScrolled from "react-stay-scrolled";
import NextLink from "next/link";
@@ -17,9 +17,18 @@ interface ILogBox {
title: string;
clearLog?: () => void;
logs: ILog[];
renderNav?: () => ReactNode;
enhanced?: boolean;
}
const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children }) => {
const LogBox: React.FC<ILogBox> = ({
title,
clearLog,
logs,
children,
renderNav,
enhanced,
}) => {
const logRef = useRef<HTMLPreElement>(null);
const { stayScrolled /*, scrollBottom*/ } = useStayScrolled(logRef);
@@ -36,10 +45,23 @@ const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children }) => {
background: "$mauve1",
position: "relative",
flex: 1,
height: "100%",
}}
>
<Container css={{ px: 0, flexShrink: 1 }}>
<Flex css={{ py: "$3" }}>
<Container
css={{
px: 0,
height: "100%",
}}
>
<Flex
css={{
height: "48px",
alignItems: "center",
fontSize: "$sm",
fontWeight: 300,
}}
>
<Heading
as="h3"
css={{
@@ -56,6 +78,7 @@ const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children }) => {
>
<Notepad size="15px" /> <Text css={{ lineHeight: 1 }}>{title}</Text>
</Heading>
{renderNav?.()}
<Flex css={{ ml: "auto", gap: "$3", marginRight: "$3" }}>
{clearLog && (
<Button ghost size="xs" onClick={clearLog}>
@@ -64,6 +87,7 @@ const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children }) => {
)}
</Flex>
</Flex>
<Box
as="pre"
ref={logRef}
@@ -73,21 +97,29 @@ const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children }) => {
display: "flex",
flexDirection: "column",
width: "100%",
height: "160px",
height: "calc(100% - 48px)", // 100% minus the logbox header height
overflowY: "auto",
fontSize: "13px",
fontWeight: "$body",
fontFamily: "$monospace",
px: "$3",
pb: "$2",
whiteSpace: "normal",
overflowY: "auto",
}}
>
{logs?.map((log, index) => (
<Box as="span" key={log.type + index}>
{/* <LogText capitalize variant={log.type}>
{log.type}:{" "}
</LogText> */}
<Box
as="span"
key={log.type + index}
css={{
"@hover": {
"&:hover": {
backgroundColor: enhanced ? "$backgroundAlt" : undefined,
},
},
p: enhanced ? "$2 $1" : undefined,
}}
>
<LogText variant={log.type}>
{log.message}{" "}
{log.link && (

View File

@@ -4,19 +4,20 @@ const Text = styled("span", {
fontFamily: "$monospace",
lineHeight: "$body",
color: "$text",
wordWrap: "break-word",
variants: {
variant: {
log: {
color: "$text",
},
warning: {
color: "$yellow11",
color: "$warning",
},
error: {
color: "$red11",
color: "$error",
},
success: {
color: "$green11",
color: "$success",
},
},
capitalize: {

View File

@@ -1,8 +1,8 @@
import { styled } from "../stitches.config";
const SVG = styled("svg", {
"& #path-one, & #path-two": {
fill: "$text",
"& #path": {
fill: "$accent",
},
});
function Logo({
@@ -14,21 +14,18 @@ function Logo({
}) {
return (
<SVG
width={width || "1em"}
height={height || "1em"}
viewBox="0 0 28 22"
width={width || "1.1em"}
height={height || "1.1em"}
viewBox="0 0 294 283"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
id="path-one"
d="M19.603 3.87h2.3l-4.786 4.747a4.466 4.466 0 01-6.276 0L6.054 3.871h2.3l3.636 3.605a2.828 2.828 0 003.975 0l3.638-3.605zM8.325 17.069h-2.3l4.816-4.776a4.466 4.466 0 016.276 0l4.816 4.776h-2.3l-3.665-3.635a2.828 2.828 0 00-3.975 0l-3.668 3.635z"
/>
<path
id="path-two"
fillRule="evenodd"
clipRule="evenodd"
d="M1.556 9.769h4.751v1.555H1.556v10.072H0V0h1.556v9.769zM26.444 9.769h-4.751v1.555h4.751v10.072H28V0h-1.556v9.769z"
id="path"
d="M265.827 235L172.416 141.589L265.005 49H226.822L147.732 128.089H53.5514L27.4824 155.089H147.732L227.643 235H265.827Z"
fill="#9D2DFF"
/>
</SVG>
);

View File

@@ -27,6 +27,7 @@ import {
DialogTrigger,
} from "./Dialog";
import PanelBox from "./PanelBox";
import { templateFileIds } from "../state/constants";
const Navigation = () => {
const router = useRouter();
@@ -39,9 +40,11 @@ const Navigation = () => {
as="nav"
css={{
display: "flex",
backgroundColor: "$mauve1",
borderBottom: "1px solid $mauve6",
position: "relative",
zIndex: 2003,
height: "60px",
}}
>
<Container
@@ -59,7 +62,7 @@ const Navigation = () => {
pr: "$4",
}}
>
<Link href="/" passHref>
<Link href={gistId ? `/develop/${gistId}` : "/develop"} passHref>
<Box
as="a"
css={{
@@ -68,7 +71,7 @@ const Navigation = () => {
color: "$textColor",
}}
>
<Logo width="30px" height="30px" />
<Logo width="32px" height="32px" />
</Box>
</Link>
<Flex
@@ -89,10 +92,25 @@ const Navigation = () => {
css={{ fontSize: "$xs", color: "$mauve10", lineHeight: 1 }}
>
{snap.files.length > 0 ? "Gist: " : "Playground"}
<Text css={{ color: "$mauve12" }}>
{snap.files.length > 0 &&
`${snap.gistOwner || "-"}/${truncate(snap.gistId || "")}`}
</Text>
{snap.files.length > 0 && (
<Link
href={`https://gist.github.com/${snap.gistOwner || ""}/${
snap.gistId || ""
}`}
passHref
>
<Text
as="a"
target="_blank"
rel="noreferrer noopener"
css={{ color: "$mauve12" }}
>
{`${snap.gistOwner || "-"}/${truncate(
snap.gistId || ""
)}`}
</Text>
</Link>
)}
</Text>
</>
)}
@@ -136,10 +154,12 @@ const Navigation = () => {
flexDirection: "column",
p: "$7",
height: "100%",
backgroundColor: "$mauve2",
"@md": {
width: "30%",
maxWidth: "300px",
borderBottom: "0px",
borderRight: "1px solid $colors$mauve5",
borderRight: "1px solid $colors$mauve6",
},
}}
>
@@ -150,9 +170,11 @@ const Navigation = () => {
alignItems: "center",
gap: "$3",
fontSize: "$xl",
lineHeight: "$one",
fontWeight: "$bold",
}}
>
<Logo width="30px" height="30px" /> XRPL Hooks Editor
<Logo width="48px" height="48px" /> XRPL Hooks Builder
</DialogTitle>
<DialogDescription as="div">
<Text
@@ -174,9 +196,9 @@ const Navigation = () => {
display: "inline-flex",
alignItems: "center",
gap: "$3",
color: "$green9",
color: "$purple10",
"&:hover": {
color: "$green11 !important",
color: "$purple11",
},
"&:focus": {
outline: 0,
@@ -187,7 +209,7 @@ const Navigation = () => {
target="_blank"
href="https://github.com/XRPL-Labs/xrpld-hooks"
>
<ArrowUpRight size="15px" /> Developing Hooks
<ArrowUpRight size="15px" /> Hooks Github
</Text>
<Text
@@ -195,9 +217,9 @@ const Navigation = () => {
display: "inline-flex",
alignItems: "center",
gap: "$3",
color: "$green9",
color: "$purple10",
"&:hover": {
color: "$green11 !important",
color: "$purple11",
},
"&:focus": {
outline: 0,
@@ -215,9 +237,9 @@ const Navigation = () => {
display: "inline-flex",
alignItems: "center",
gap: "$3",
color: "$green9",
color: "$purple10",
"&:hover": {
color: "$green11 !important",
color: "$purple11",
},
"&:focus": {
outline: 0,
@@ -244,9 +266,7 @@ const Navigation = () => {
gap: "$3",
alignItems: "flex-start",
flexWrap: "wrap",
backgroundImage: `url('/pattern.svg'), url('/pattern-2.svg')`,
backgroundRepeat: "no-repeat",
backgroundPosition: "bottom left, top right",
backgroundColor: "$mauve1",
"@md": {
gridTemplateColumns: "1fr 1fr 1fr",
gridTemplateRows: "max-content",
@@ -255,16 +275,16 @@ const Navigation = () => {
>
<PanelBox
as="a"
href="/develop/be088224fb37c0075e84491da0e602c1"
href={`/develop/${templateFileIds.starter}`}
>
<Heading>Starter</Heading>
<Text>
Just an empty starter with essential imports
Just a basic starter with essential imports
</Text>
</PanelBox>
<PanelBox
as="a"
href="/develop/be088224fb37c0075e84491da0e602c1"
href={`/develop/${templateFileIds.firewall}`}
>
<Heading>Firewall</Heading>
<Text>
@@ -273,23 +293,26 @@ const Navigation = () => {
</PanelBox>
<PanelBox
as="a"
href="/develop/be088224fb37c0075e84491da0e602c1"
href={`/develop/${templateFileIds.notary}`}
>
<Heading>Accept</Heading>
<Heading>Notary</Heading>
<Text>
This hook just accepts any transaction coming through
it
Collecting signatures for multi-sign transactions
</Text>
</PanelBox>
<PanelBox
as="a"
href="/develop/be088224fb37c0075e84491da0e602c1"
href={`/develop/${templateFileIds.carbon}`}
>
<Heading>Accept</Heading>
<Text>
This hook just accepts any transaction coming through
it
</Text>
<Heading>Carbon</Heading>
<Text>Send a percentage of sum to an address</Text>
</PanelBox>
<PanelBox
as="a"
href={`/develop/${templateFileIds.peggy}`}
>
<Heading>Peggy</Heading>
<Text>An oracle based stabe coin hook</Text>
</PanelBox>
</Flex>
</Flex>
@@ -376,9 +399,13 @@ const Navigation = () => {
</Button>
</Link>
</ButtonGroup>
<Button outline disabled>
<BookOpen size="15px" />
</Button>
<Link href="https://xrpl-hooks.readme.io/" passHref>
<a target="_blank" rel="noreferrer noopener">
<Button outline>
<BookOpen size="15px" />
</Button>
</a>
</Link>
</Stack>
</Flex>
</Container>

View File

@@ -5,8 +5,8 @@ import Text from "./Text";
const PanelBox = styled("div", {
display: "flex",
flexDirection: "column",
border: "1px solid $colors$mauve5",
backgroundColor: "$mauve1",
border: "1px solid $colors$mauve6",
backgroundColor: "$mauve2",
padding: "$3",
borderRadius: "$sm",
fontWeight: "lighter",

56
components/Select.tsx Normal file
View File

@@ -0,0 +1,56 @@
import { FC } from "react";
import { mauve, mauveDark } 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 });
const Select: FC<Props> = props => {
const { theme } = useTheme();
const isDark = theme === "dark";
const colors: any = {
// primary: pink.pink9,
primary: isDark ? mauveDark.mauve4 : mauve.mauve4,
secondary: isDark ? mauveDark.mauve8 : mauve.mauve8,
background: isDark ? "rgb(10, 10, 10)" : "rgb(245, 245, 245)",
searchText: isDark ? mauveDark.mauve12 : mauve.mauve12,
placeholder: isDark ? mauveDark.mauve11 : mauve.mauve11,
};
colors.outline = colors.background;
colors.selected = colors.secondary;
return (
<SelectInput
menuPosition="fixed"
theme={theme => ({
...theme,
spacing: {
...theme.spacing,
controlHeight: 30
},
colors: {
primary: colors.selected,
primary25: colors.primary,
primary50: colors.primary,
primary75: colors.primary,
danger: colors.primary,
dangerLight: colors.primary,
neutral0: colors.background,
neutral5: colors.primary,
neutral10: colors.primary,
neutral20: colors.outline,
neutral30: colors.primary,
neutral40: colors.primary,
neutral50: colors.placeholder,
neutral60: colors.primary,
neutral70: colors.primary,
neutral80: colors.searchText,
neutral90: colors.primary,
},
})}
{...props}
/>
);
};
export default styled(Select, {});

257
components/Tabs.tsx Normal file
View File

@@ -0,0 +1,257 @@
import React, {
useEffect,
useState,
Fragment,
isValidElement,
useCallback,
} from "react";
import type { ReactNode, ReactElement } from "react";
import { Box, Button, Flex, Input, Stack, Text } from ".";
import {
Dialog,
DialogTrigger,
DialogContent,
DialogTitle,
DialogDescription,
DialogClose,
} from "./Dialog";
import { Plus, X } from "phosphor-react";
import { styled } from "../stitches.config";
const ErrorText = styled(Text, {
color: "$error",
mt: "$1",
display: "block",
});
interface TabProps {
header?: string;
children: ReactNode;
}
// TODO customise strings shown
interface Props {
activeIndex?: number;
activeHeader?: string;
headless?: boolean;
children: ReactElement<TabProps>[];
keepAllAlive?: boolean;
defaultExtension?: string;
forceDefaultExtension?: boolean;
onCreateNewTab?: (name: string) => any;
onCloseTab?: (index: number, header?: string) => any;
}
export const Tab = (props: TabProps) => null;
export const Tabs = ({
children,
activeIndex,
activeHeader,
headless,
keepAllAlive = false,
onCreateNewTab,
onCloseTab,
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);
setActive(idx);
}
}, [activeHeader, tabs]);
// when filename changes, reset error
useEffect(() => {
setNewtabError(null);
}, [tabname, setNewtabError]);
const validateTabname = useCallback(
(tabname: string): { error: string | null } => {
if (tabs.find((tab) => tab.header === tabname)) {
return { error: "Name already exists." };
}
return { error: null };
},
[tabs]
);
const handleCreateTab = useCallback(() => {
// add default extension in case omitted
let _tabname = tabname.includes(".") ? tabname : tabname + defaultExtension;
if (forceDefaultExtension && !_tabname.endsWith(defaultExtension)) {
_tabname = _tabname + defaultExtension;
}
const chk = validateTabname(_tabname);
if (chk.error) {
setNewtabError(`Error: ${chk.error}`);
return;
}
setIsNewtabDialogOpen(false);
setTabname("");
// switch to new tab?
setActive(tabs.length);
onCreateNewTab?.(_tabname);
}, [tabname, defaultExtension, validateTabname, onCreateNewTab, tabs.length]);
const handleCloseTab = useCallback(
(idx: number) => {
if (idx <= active && active !== 0) {
setActive(active - 1);
}
onCloseTab?.(idx, tabs[idx].header);
},
[active, onCloseTab, tabs]
);
return (
<>
{!headless && (
<Stack
css={{
gap: "$3",
flex: 1,
flexWrap: "nowrap",
marginBottom: "-1px",
width: "100%",
overflow: "auto",
}}
>
{tabs.map((tab, idx) => (
<Button
key={tab.header}
role="tab"
tabIndex={idx}
onClick={() => setActive(idx)}
onKeyPress={() => setActive(idx)}
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>
)}
</>
);
};

View File

@@ -4,6 +4,18 @@ const Text = styled("span", {
fontFamily: "$body",
lineHeight: "$body",
color: "$text",
variants: {
small: {
true: {
fontSize: '$xs'
}
},
muted: {
true: {
color: '$mauve9'
}
}
}
});
export default Text;

16
components/index.tsx Normal file
View File

@@ -0,0 +1,16 @@
export { default as Flex } from "./Flex";
export { default as Link } from "./Link";
export { default as Container } from "./Container";
export { default as Heading } from "./Heading";
export { default as Stack } from "./Stack";
export { default as Text } from "./Text";
export { default as Input } from "./Input";
export { default as Select } from "./Select";
export * from "./Tabs";
export * from "./AlertDialog";
export { default as Box } from "./Box";
export { default as Button } from "./Button";
export { default as ButtonGroup } from "./ButtonGroup";
export { default as DeployFooter } from "./DeployFooter";
export * from "./Dialog";
export * from "./DropdownMenu";

221
content/transactions.json Normal file
View 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": "currency"
},
"CheckID": "838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334",
"Fee": "12"
},
{
"TransactionType": "CheckCreate",
"Account": "rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo",
"Destination": "rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy",
"SendMax": "100000000",
"Expiration": 570113521,
"InvoiceID": "6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B",
"DestinationTag": 1,
"Fee": "12"
},
{
"TransactionType": "DepositPreauth",
"Account": "rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8",
"Authorize": "rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de",
"Fee": "10",
"Flags": 2147483648,
"Sequence": 2
},
{
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"TransactionType": "EscrowCancel",
"Owner": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"OfferSequence": 7
},
{
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"TransactionType": "EscrowCreate",
"Amount": {
"value": "100",
"type": "currency"
},
"Destination": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
"CancelAfter": 533257958,
"FinishAfter": 533171558,
"Condition": "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100",
"DestinationTag": 23480,
"SourceTag": 11747
},
{
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"TransactionType": "EscrowFinish",
"Owner": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"OfferSequence": 7,
"Condition": "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100",
"Fulfillment": "A0028000"
},
{
"TransactionType": "NFTokenBurn",
"Account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"Fee": "10",
"TokenID": "000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65"
},
{
"TransactionType": "NFTokenAcceptOffer",
"Fee": "10"
},
{
"TransactionType": "NFTokenCancelOffer",
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"TokenIDs": "000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007"
},
{
"TransactionType": "NFTokenCreateOffer",
"Account": "rs8jBmmfpwgmrSPgwMsh7CvKRmRt1JTVSX",
"TokenID": "000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007",
"Amount": {
"value": "100",
"type": "currency"
},
"Flags": 1
},
{
"TransactionType": "OfferCancel",
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"Fee": "12",
"Flags": 0,
"LastLedgerSequence": 7108629,
"OfferSequence": 6,
"Sequence": 7
},
{
"TransactionType": "OfferCreate",
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"Fee": "12",
"Flags": 0,
"LastLedgerSequence": 7108682,
"Sequence": 8,
"TakerGets": "6000000",
"Amount": {
"value": "100",
"type": "currency"
}
},
{
"TransactionType": "Payment",
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Destination": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"Amount": {
"value": "100",
"type": "currency"
},
"Fee": "12",
"Flags": 2147483648,
"Sequence": 2
},
{
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"TransactionType": "PaymentChannelCreate",
"Amount": {
"value": "100",
"type": "currency"
},
"Destination": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
"SettleDelay": 86400,
"PublicKey": "32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A",
"CancelAfter": 533171558,
"DestinationTag": 23480,
"SourceTag": 11747
},
{
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"TransactionType": "PaymentChannelFund",
"Channel": "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198",
"Amount": {
"value": "200",
"type": "currency"
},
"Expiration": 543171558
},
{
"Flags": 0,
"TransactionType": "SetRegularKey",
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Fee": "12",
"RegularKey": "rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD"
},
{
"Flags": 0,
"TransactionType": "SignerListSet",
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Fee": "12",
"SignerQuorum": 3,
"SignerEntries": {
"type": "json",
"value": [
{
"SignerEntry": {
"Account": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
"SignerWeight": 2
}
},
{
"SignerEntry": {
"Account": "rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v",
"SignerWeight": 1
}
},
{
"SignerEntry": {
"Account": "raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n",
"SignerWeight": 1
}
}
]
}
},
{
"TransactionType": "TicketCreate",
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Fee": "10",
"Sequence": 381,
"TicketCount": 10
},
{
"TransactionType": "TrustSet",
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"Fee": "12",
"Flags": 262144,
"LastLedgerSequence": 8007750,
"Amount": {
"value": "100",
"type": "currency"
},
"Sequence": 12
}
]

View File

@@ -20,10 +20,12 @@
"@radix-ui/react-dropdown-menu": "^0.1.1",
"@radix-ui/react-id": "^0.1.1",
"@stitches/react": "^1.2.6-0",
"file-saver": "^2.0.5",
"jszip": "^3.7.1",
"base64-js": "^1.5.1",
"dinero.js": "^1.9.1",
"file-saver": "^2.0.5",
"filesize": "^8.0.7",
"javascript-time-ago": "^2.3.11",
"jszip": "^3.7.1",
"monaco-editor": "^0.30.1",
"next": "^12.0.4",
"next-auth": "^4.0.0-beta.5",
@@ -40,7 +42,10 @@
"react-hot-keys": "^2.7.1",
"react-hot-toast": "^2.1.1",
"react-new-window": "^0.2.1",
"react-select": "^5.2.1",
"react-split": "^2.0.14",
"react-stay-scrolled": "^7.4.0",
"react-time-ago": "^7.1.9",
"reconnecting-websocket": "^4.4.0",
"valtio": "^1.2.5",
"vscode-languageserver": "^7.0.0",
@@ -50,8 +55,8 @@
"xrpl-client": "^1.9.3"
},
"devDependencies": {
"@types/file-saver": "^2.0.4",
"@types/dinero.js": "^1.9.0",
"@types/file-saver": "^2.0.4",
"@types/pako": "^1.0.2",
"@types/react": "17.0.31",
"eslint": "7.32.0",

View File

@@ -13,10 +13,18 @@ import Navigation from "../components/Navigation";
import { fetchFiles } from "../state/actions";
import state from "../state";
import TimeAgo from "javascript-time-ago";
import en from "javascript-time-ago/locale/en.json";
TimeAgo.addDefaultLocale(en);
function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
const router = useRouter();
const slug = router.query?.slug;
const gistId = (Array.isArray(slug) && slug[0]) ?? null;
const origin = "https://xrpl-hooks-ide.vercel.app"; // TODO: Change when site is deployed
const shareImg = "/share-image.png";
useEffect(() => {
if (gistId && router.isReady) {
fetchFiles(gistId);
@@ -30,7 +38,73 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
return (
<>
<Head>
<title>XRPL Hooks Playground</title>
<meta charSet="utf-8" />
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<meta name="format-detection" content="telephone=no" />
<meta property="og:url" content={`${origin}${router.asPath}`} />
<title>XRPL Hooks Editor</title>
<meta property="og:title" content="XRPL Hooks Editor" />
<meta name="twitter:title" content="XRPL Hooks Editor" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@xrpllabs" />
<meta
name="description"
content="Playground for buildings Hooks, that add smart contract functionality to the XRP Ledger."
/>
<meta
property="og:description"
content="Playground for buildings Hooks, that add smart contract functionality to the XRP Ledger."
/>
<meta
name="twitter:description"
content="Playground for buildings Hooks, that add smart contract functionality to the XRP Ledger.."
/>
<meta property="og:image" content={`${origin}${shareImg}`} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:image" content={`${origin}${shareImg}`} />
<link
rel="apple-touch-icon"
sizes="180x180"
href="/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon-16x16.png"
/>
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#161618" />
<meta name="application-name" content="XRPL Hooks Editor" />
<meta name="msapplication-TileColor" content="#c10ad0" />
<meta
name="theme-color"
content="#161618"
media="(prefers-color-scheme: dark)"
/>
<meta
name="theme-color"
content="#FDFCFD"
media="(prefers-color-scheme: light)"
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossOrigin=""
/>
<link
href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital@0;1&family=Work+Sans:wght@400;600;700&display=swap"
rel="stylesheet"
/>
</Head>
<IdProvider>
<SessionProvider session={session}>

View File

@@ -16,21 +16,10 @@ class MyDocument extends Document {
}
render() {
globalStyles();
return (
<Html>
<Head>
<meta name="description" content="Playground for XRPL Hooks" />
<link rel="icon" href="/favicon.ico" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossOrigin=""
/>
<link
href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital@0;1&family=Work+Sans:wght@400;600;700&display=swap"
rel="stylesheet"
/>
<style
id="stitches"
dangerouslySetInnerHTML={{ __html: getCssText() }}

View File

@@ -1,8 +1,8 @@
import React from "react";
import dynamic from "next/dynamic";
import Flex from "../../components/Flex";
import { useSnapshot } from "valtio";
import state from "../../state";
import Split from "react-split";
const DeployEditor = dynamic(() => import("../../components/DeployEditor"), {
ssr: false,
@@ -19,19 +19,41 @@ const LogBox = dynamic(() => import("../../components/LogBox"), {
const Deploy = () => {
const snap = useSnapshot(state);
return (
<>
<main style={{ display: "flex", flex: 1 }}>
<Split
direction="vertical"
gutterSize={4}
gutterAlign="center"
sizes={[40, 60]}
style={{ height: "calc(100vh - 60px)" }}
>
<main style={{ display: "flex", flex: 1, position: "relative" }}>
<DeployEditor />
</main>
<Flex css={{ flexDirection: "row", width: "100%" }}>
<Accounts />
<LogBox
title="Deploy Log"
logs={snap.deployLogs}
clearLog={() => (state.deployLogs = [])}
/>
</Flex>
</>
<Split
direction="horizontal"
sizes={[50, 50]}
minSize={[320, 160]}
gutterSize={4}
gutterAlign="center"
style={{
display: "flex",
flexDirection: "row",
width: "100%",
height: "100%",
}}
>
<div style={{ alignItems: "stretch", display: "flex" }}>
<Accounts />
</div>
<div>
<LogBox
title="Deploy Log"
logs={snap.deployLogs}
clearLog={() => (state.deployLogs = [])}
/>
</div>
</Split>
</Split>
);
};

View File

@@ -2,6 +2,7 @@ import dynamic from "next/dynamic";
import { useSnapshot } from "valtio";
import Hotkeys from "react-hot-keys";
import { Play } from "phosphor-react";
import Split from "react-split";
import type { NextPage } from "next";
import { compileCode } from "../../state/actions";
@@ -19,8 +20,16 @@ const LogBox = dynamic(() => import("../../components/LogBox"), {
const Home: NextPage = () => {
const snap = useSnapshot(state);
return (
<>
<Split
direction="vertical"
sizes={[70, 30]}
minSize={[100, 100]}
gutterAlign="center"
gutterSize={4}
style={{ height: "calc(100vh - 60px)" }}
>
<main style={{ display: "flex", flex: 1, position: "relative" }}>
<HooksEditor />
{snap.files[snap.active]?.name?.split(".")?.[1].toLowerCase() ===
@@ -65,7 +74,7 @@ const Home: NextPage = () => {
logs={snap.logs}
/>
</Box>
</>
</Split>
);
};

View File

@@ -1,7 +1,445 @@
import Container from "../../components/Container";
import {
Container,
Flex,
Box,
Tabs,
Tab,
Input,
Select,
Text,
Button,
} from "../../components";
import { Play } from "phosphor-react";
import dynamic from "next/dynamic";
import { useSnapshot } from "valtio";
import Split from "react-split";
import state from "../../state";
import { sendTransaction } from "../../state/actions";
import { useCallback, useEffect, useState, FC } from "react";
import transactionsData from "../../content/transactions.json";
const DebugStream = dynamic(() => import("../../components/DebugStream"), {
ssr: false,
});
const LogBox = dynamic(() => import("../../components/LogBox"), {
ssr: false,
});
const Accounts = dynamic(() => import("../../components/Accounts"), {
ssr: false,
});
// type SelectOption<T> = { value: T, label: string };
type TxFields = Omit<
typeof transactionsData[0],
"Account" | "Sequence" | "TransactionType"
>;
type OtherFields = (keyof Omit<TxFields, "Destination">)[];
interface Props {
header?: string;
}
const Transaction: FC<Props> = ({ header, ...props }) => {
const snap = useSnapshot(state);
const transactionsOptions = transactionsData.map((tx) => ({
value: tx.TransactionType,
label: tx.TransactionType,
}));
const [selectedTransaction, setSelectedTransaction] = useState<
typeof transactionsOptions[0] | null
>(null);
const accountOptions = snap.accounts.map((acc) => ({
label: acc.name,
value: acc.address,
}));
const [selectedAccount, setSelectedAccount] = useState<
typeof accountOptions[0] | null
>(null);
const destAccountOptions = snap.accounts
.map((acc) => ({
label: acc.name,
value: acc.address,
}))
.filter((acc) => acc.value !== selectedAccount?.value);
const [selectedDestAccount, setSelectedDestAccount] = useState<
typeof destAccountOptions[0] | null
>(null);
const [txIsLoading, setTxIsLoading] = useState(false);
const [txIsDisabled, setTxIsDisabled] = useState(false);
const [txFields, setTxFields] = useState<TxFields>({});
useEffect(() => {
const transactionType = selectedTransaction?.value;
const account = snap.accounts.find(
(acc) => acc.address === selectedAccount?.value
);
if (!account || !transactionType || txIsLoading) {
setTxIsDisabled(true);
} else {
setTxIsDisabled(false);
}
}, [txIsLoading, selectedTransaction, selectedAccount, snap.accounts]);
useEffect(() => {
let _txFields: TxFields | undefined = transactionsData.find(
(tx) => tx.TransactionType === selectedTransaction?.value
);
if (!_txFields) return setTxFields({});
_txFields = { ..._txFields } as TxFields;
setSelectedDestAccount(null);
// @ts-ignore
delete _txFields.TransactionType;
// @ts-ignore
delete _txFields.Account;
// @ts-ignore
delete _txFields.Sequence;
setTxFields(_txFields);
}, [selectedTransaction, setSelectedDestAccount]);
const submitTest = useCallback(async () => {
const account = snap.accounts.find(
(acc) => acc.address === selectedAccount?.value
);
const TransactionType = selectedTransaction?.value;
if (!account || !TransactionType || txIsDisabled) return;
setTxIsLoading(true);
// setTxIsError(null)
try {
let options = { ...txFields };
options.Destination = selectedDestAccount?.value;
(Object.keys(options) as (keyof TxFields)[]).forEach((field) => {
let _value = options[field];
// convert currency
if (typeof _value === "object" && _value.type === "currency") {
if (+_value.value) {
options[field] = (+_value.value * 1000000 + "") as any;
} else {
options[field] = undefined; // 👇 💀
}
}
// handle type: `json`
if (typeof _value === "object" && _value.type === "json") {
if (typeof _value.value === "object") {
options[field] = _value.value as any;
} else {
try {
options[field] = JSON.parse(_value.value);
} catch (error) {
const message = `Input error for json field '${field}': ${
error instanceof Error ? error.message : ""
}`;
throw Error(message);
}
}
}
// delete unneccesary fields
if (!options[field]) {
delete options[field];
}
});
const logPrefix = header ? `${header.split(".")[0]}: ` : undefined;
await sendTransaction(
account,
{
TransactionType,
...options,
},
{ logPrefix }
);
} catch (error) {
console.error(error);
if (error instanceof Error) {
state.transactionLogs.push({ type: "error", message: error.message });
}
}
setTxIsLoading(false);
}, [
header,
selectedAccount?.value,
selectedDestAccount?.value,
selectedTransaction?.value,
snap.accounts,
txFields,
txIsDisabled,
]);
const resetState = useCallback(() => {
setSelectedAccount(null);
setSelectedDestAccount(null);
setSelectedTransaction(null);
setTxFields({});
setTxIsDisabled(false);
setTxIsLoading(false);
}, []);
const usualFields = ["TransactionType", "Amount", "Account", "Destination"];
const otherFields = Object.keys(txFields).filter(
(k) => !usualFields.includes(k)
) as OtherFields;
return (
<Box css={{ position: "relative", height: "calc(100% - 28px)" }} {...props}>
<Container
css={{ p: "$3 0", fontSize: "$sm", height: "calc(100% - 28px)" }}
>
<Flex column fluid css={{ height: "100%", overflowY: "auto" }}>
<Flex
row
fluid
css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}
>
<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) => setSelectedTransaction(tt as any)}
/>
</Flex>
<Flex
row
fluid
css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}
>
<Text muted css={{ mr: "$3" }}>
Account:{" "}
</Text>
<Select
instanceId="from-account"
placeholder="Select your account"
css={{ width: "70%" }}
options={accountOptions}
value={selectedAccount}
onChange={(acc) => setSelectedAccount(acc as any)}
/>
</Flex>
{txFields.Amount !== undefined && (
<Flex
row
fluid
css={{
justifyContent: "flex-end",
alignItems: "center",
mb: "$3",
}}
>
<Text muted css={{ mr: "$3" }}>
Amount (XRP):{" "}
</Text>
<Input
value={txFields.Amount.value}
onChange={(e) =>
setTxFields({
...txFields,
Amount: { type: "currency", value: e.target.value },
})
}
variant="deep"
css={{ width: "70%", flex: "inherit", height: "$9" }}
/>
</Flex>
)}
{txFields.Destination !== undefined && (
<Flex
row
fluid
css={{
justifyContent: "flex-end",
alignItems: "center",
mb: "$3",
}}
>
<Text muted css={{ mr: "$3" }}>
Destination account:{" "}
</Text>
<Select
instanceId="to-account"
placeholder="Select the destination account"
css={{ width: "70%" }}
options={destAccountOptions}
value={selectedDestAccount}
isClearable
onChange={(acc) => setSelectedDestAccount(acc as any)}
/>
</Flex>
)}
{otherFields.map((field) => {
let _value = txFields[field];
let value = typeof _value === "object" ? _value.value : _value;
value =
typeof value === "object"
? JSON.stringify(value)
: value?.toLocaleString();
let isCurrency =
typeof _value === "object" && _value.type === "currency";
return (
<Flex
key={field}
row
fluid
css={{
justifyContent: "flex-end",
alignItems: "center",
mb: "$3",
}}
>
<Text muted css={{ mr: "$3" }}>
{field + (isCurrency ? " (XRP)" : "")}:{" "}
</Text>
<Input
value={value}
onChange={(e) =>
setTxFields({
...txFields,
[field]:
typeof _value === "object"
? { ..._value, value: e.target.value }
: e.target.value,
})
}
variant="deep"
css={{ width: "70%", flex: "inherit", height: "$9" }}
/>
</Flex>
);
})}
</Flex>
</Container>
<Flex
row
css={{
justifyContent: "space-between",
position: "absolute",
left: 0,
bottom: 0,
width: "100%",
}}
>
<Button outline>VIEW AS JSON</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>
);
};
const Test = () => {
return <Container css={{ py: "$10" }}>This will be the test page</Container>;
const snap = useSnapshot(state);
const [tabHeaders, setTabHeaders] = useState<string[]>(["test1.json"]);
return (
<Container css={{ px: 0 }}>
<Split
direction="vertical"
sizes={[50, 50]}
gutterSize={4}
gutterAlign="center"
style={{ height: "calc(100vh - 60px)" }}
>
<Flex
row
fluid
css={{
justifyContent: "center",
p: "$3 $2",
}}
>
<Split
direction="horizontal"
sizes={[50, 50]}
minSize={[180, 320]}
gutterSize={4}
gutterAlign="center"
style={{
display: "flex",
flexDirection: "row",
width: "100%",
height: "100%",
}}
>
<Box css={{ width: "55%", px: "$2" }}>
<Tabs
keepAllAlive
forceDefaultExtension
defaultExtension=".json"
onCreateNewTab={(name) =>
setTabHeaders(tabHeaders.concat(name))
}
onCloseTab={(index) =>
setTabHeaders(tabHeaders.filter((_, idx) => idx !== index))
}
>
{tabHeaders.map((header) => (
<Tab key={header} header={header}>
<Transaction header={header} />
</Tab>
))}
</Tabs>
</Box>
<Box css={{ width: "45%", mx: "$2", height: "100%" }}>
<Accounts card hideDeployBtn showHookStats />
</Box>
</Split>
</Flex>
<Flex row fluid>
<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={snap.transactionLogs}
clearLog={() => (state.transactionLogs = [])}
/>
</Box>
<Box css={{ height: "100%" }}>
<DebugStream />
</Box>
</Split>
</Flex>
</Split>
</Container>
);
};
export default Test;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

9
public/browserconfig.xml Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 737 B

BIN
public/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 15 KiB

BIN
public/mstile-150x150.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 KiB

19
public/site.webmanifest Normal file
View 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"
}

View File

@@ -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

View File

@@ -25,6 +25,7 @@ export const compileCode = async (activeId: number) => {
}
// Set loading state to true
state.compiling = true;
state.logs = []
try {
const res = await fetch(process.env.NEXT_PUBLIC_COMPILE_API_ENDPOINT, {
method: "POST",
@@ -38,7 +39,6 @@ export const compileCode = async (activeId: number) => {
{
type: "c",
name: state.files[activeId].name,
options: "-O0",
src: state.files[activeId].content,
},
],
@@ -66,6 +66,7 @@ export const compileCode = async (activeId: number) => {
// Decode base64 encoded wasm that is coming back from the endpoint
const bufferData = await decodeBinary(json.output);
state.files[state.active].compiledContent = ref(bufferData);
state.files[state.active].lastCompiled = new Date();
// Import wabt from and create human readable version of wasm file and
// put it into state
import("wabt").then((wabt) => {
@@ -87,4 +88,4 @@ export const compileCode = async (activeId: number) => {
});
state.compiling = false;
}
};
};

View File

@@ -1,6 +1,8 @@
import { Octokit } from "@octokit/core";
import Router from "next/router";
import state from '../index';
import { templateFileIds } from '../constants';
const octokit = new Octokit();
@@ -17,6 +19,17 @@ export const fetchFiles = (gistId: string) => {
octokit
.request("GET /gists/{gist_id}", { gist_id: gistId })
.then(res => {
if (!Object.values(templateFileIds).includes(gistId)) {
return res
}
// in case of templates, fetch header file(s) and append to res
return octokit.request("GET /gists/{gist_id}", { gist_id: templateFileIds.headers }).then(({ data: { files: headerFiles } }) => {
const files = { ...res.data.files, ...headerFiles }
res.data.files = files
return res
})
})
.then((res) => {
if (res.data.files && Object.keys(res.data.files).length > 0) {
const files = Object.keys(res.data.files).map((filename) => ({
@@ -44,6 +57,7 @@ export const fetchFiles = (gistId: string) => {
state.loading = false;
})
.catch((err) => {
// console.error(err)
state.loading = false;
state.logs.push({
type: "error",

View File

@@ -8,6 +8,7 @@ import { saveFile } from "./saveFile";
import { syncToGist } from "./syncToGist";
import { updateEditorSettings } from "./updateEditorSettings";
import { downloadAsZip } from "./downloadAsZip";
import { sendTransaction } from "./sendTransaction";
export {
addFaucetAccount,
@@ -19,5 +20,6 @@ export {
saveFile,
syncToGist,
updateEditorSettings,
downloadAsZip
downloadAsZip,
sendTransaction
};

View File

@@ -4,8 +4,9 @@ import state from '../index';
// Saves the current editor content to global state
export const saveFile = (showToast: boolean = true) => {
const editorModels = state.editorCtx?.getModels();
const sought = '/' + state.files[state.active].name;
const currentModel = editorModels?.find((editorModel) => {
return editorModel.uri.path === `/c/${state.files[state.active].name}`;
return editorModel.uri.path.endsWith(sought);
});
if (state.files.length > 0) {
state.files[state.active].content = currentModel?.getValue() || "";
@@ -13,4 +14,4 @@ export const saveFile = (showToast: boolean = true) => {
if (showToast) {
toast.success("Saved successfully", { position: "bottom-center" });
}
};
};

View File

@@ -0,0 +1,53 @@
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}`,
});
}
} 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`,
});
}
};

1
state/constants/index.ts Normal file
View File

@@ -0,0 +1 @@
export * from './templates'

View File

@@ -0,0 +1,10 @@
export const templateFileIds = {
'starter': '1d14e51e2e02dc0a508cb0733767a914', // TODO currently same as accept
'firewall': 'bcd6d0c0fcbe52545ddb802481ff9d26',
'notary': 'a789c75f591eeab7932fd702ed8cf9ea',
'carbon': '43925143fa19735d8c6505c34d3a6a47',
'peggy': 'ceaf352e2a65741341033ab7ef05c448',
'headers': '9b448e8a55fab11ef5d1274cb59f9cf3'
}
export const apiHeaderFiles = ['hookapi.h', 'sfcodes.h', 'hookmacro.h']

View File

@@ -9,6 +9,7 @@ export interface IFile {
content: string;
compiledContent?: ArrayBuffer | null;
compiledWatContent?: string | null;
lastCompiled?: Date
}
export interface FaucetAccountRes {
@@ -49,6 +50,8 @@ export interface IState {
compiling: boolean;
logs: ILog[];
deployLogs: ILog[];
transactionLogs: ILog[];
debugLogs: ILog[];
editorCtx?: typeof monaco.editor;
editorSettings: {
tabSize: number;
@@ -70,6 +73,8 @@ let initialState: IState = {
compiling: false,
logs: [],
deployLogs: [],
transactionLogs: [],
debugLogs: [],
editorCtx: undefined,
gistId: undefined,
gistOwner: undefined,

View File

@@ -1,28 +1,27 @@
// stitches.config.ts
import type Stitches from '@stitches/react';
import { createStitches } from '@stitches/react';
import type Stitches from "@stitches/react";
import { createStitches } from "@stitches/react";
import {
gray,
blue,
red,
green,
plum,
crimson,
grass,
slate,
mauve,
pink,
yellow,
amber,
purple,
grayDark,
blueDark,
redDark,
greenDark,
plumDark,
crimsonDark,
grassDark,
slateDark,
mauveDark,
pinkDark,
yellowDark,
amberDark,
purpleDark,
} from '@radix-ui/colors';
red,
redDark,
} from "@radix-ui/colors";
export const {
styled,
@@ -38,24 +37,30 @@ export const {
colors: {
...gray,
...blue,
...red,
...green,
...plum,
...crimson,
...grass,
...slate,
...mauve,
...pink,
...yellow,
...amber,
...purple,
...red,
accent: "#9D2DFF",
background: "$gray1",
backgroundAlt: "$gray4",
text: "$gray12",
textMuted: "$gray10",
primary: "$plum",
error: '$red9',
warning: '$amber11',
success: "$grass11",
white: "white",
black: "black"
black: "black",
deep: "rgb(244, 244, 244)",
},
fonts: {
body: 'Work Sans, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif',
heading: 'Work Sans, sans-serif',
monospace: 'Roboto Mono, monospace',
heading: "Work Sans, sans-serif",
monospace: "Roboto Mono, monospace",
},
fontSizes: {
xs: "0.6875rem",
@@ -71,7 +76,7 @@ export const {
"7xl": "4.5rem",
"8xl": "6rem",
"9xl": "8rem",
default: '$md'
default: "$md",
},
space: {
px: "1px",
@@ -107,15 +112,15 @@ export const {
72: "18rem",
80: "20rem",
96: "24rem",
"widePlus": '2048px',
"wide": '1536px',
"layoutPlus": '1260px',
"layout": '1024px',
"copyUltra": '980px',
"copyPlus": '768px',
"copy": '680px',
"narrowPlus": '600px',
"narrow": '512px',
widePlus: "2048px",
wide: "1536px",
layoutPlus: "1260px",
layout: "1024px",
copyUltra: "980px",
copyPlus: "768px",
copy: "680px",
narrowPlus: "600px",
narrow: "512px",
xs: "20rem",
sm: "24rem",
md: "28rem",
@@ -215,62 +220,112 @@ export const {
lg: "(min-width: 62em)",
xl: "(min-width: 80em)",
"2xl": "(min-width: 96em)",
hover: '(any-hover: hover)',
dark: '(prefers-color-scheme: dark)',
light: '(prefers-color-scheme: light)',
hover: "(any-hover: hover)",
dark: "(prefers-color-scheme: dark)",
light: "(prefers-color-scheme: light)",
},
utils: {
// Abbreviated margin properties
m: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'margin'>) => ({
m: (
value: Stitches.ScaleValue<"space"> | Stitches.PropertyValue<"margin">
) => ({
margin: value,
}),
mt: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'marginTop'>) => ({
mt: (
value: Stitches.ScaleValue<"space"> | Stitches.PropertyValue<"marginTop">
) => ({
marginTop: value,
}),
mr: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'marginRight'>) => ({
mr: (
value:
| Stitches.ScaleValue<"space">
| Stitches.PropertyValue<"marginRight">
) => ({
marginRight: value,
}),
mb: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'marginBottom'>) => ({
mb: (
value:
| Stitches.ScaleValue<"space">
| Stitches.PropertyValue<"marginBottom">
) => ({
marginBottom: value,
}),
ml: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'marginLeft'>) => ({
ml: (
value: Stitches.ScaleValue<"space"> | Stitches.PropertyValue<"marginLeft">
) => ({
marginLeft: value,
}),
mx: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'marginLeft' | 'marginRight'>) => ({
mx: (
value:
| Stitches.ScaleValue<"space">
| Stitches.PropertyValue<"marginLeft" | "marginRight">
) => ({
marginLeft: value,
marginRight: value,
}),
my: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'marginTop' | 'marginBottom'>) => ({
my: (
value:
| Stitches.ScaleValue<"space">
| Stitches.PropertyValue<"marginTop" | "marginBottom">
) => ({
marginTop: value,
marginBottom: value,
}),
// Abbreviated margin properties
p: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'padding'>) => ({
p: (
value: Stitches.ScaleValue<"space"> | Stitches.PropertyValue<"padding">
) => ({
padding: value,
}),
pt: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'paddingTop'>) => ({
pt: (
value: Stitches.ScaleValue<"space"> | Stitches.PropertyValue<"paddingTop">
) => ({
paddingTop: value,
}),
pr: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'paddingRight'>) => ({
pr: (
value:
| Stitches.ScaleValue<"space">
| Stitches.PropertyValue<"paddingRight">
) => ({
paddingRight: value,
}),
pb: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'paddingBottom'>) => ({
pb: (
value:
| Stitches.ScaleValue<"space">
| Stitches.PropertyValue<"paddingBottom">
) => ({
paddingBottom: value,
}),
pl: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'paddingLeft'>) => ({
pl: (
value:
| Stitches.ScaleValue<"space">
| Stitches.PropertyValue<"paddingLeft">
) => ({
paddingLeft: value,
}),
px: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'paddingLeft' | 'paddingRight'>) => ({
px: (
value:
| Stitches.ScaleValue<"space">
| Stitches.PropertyValue<"paddingLeft" | "paddingRight">
) => ({
paddingLeft: value,
paddingRight: value,
}),
py: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'paddingTop' | 'paddingBottom'>) => ({
py: (
value:
| Stitches.ScaleValue<"space">
| Stitches.PropertyValue<"paddingTop" | "paddingBottom">
) => ({
paddingTop: value,
paddingBottom: value,
}),
// A property for applying width/height together
size: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'width' | 'height'>) => ({
size: (
value:
| Stitches.ScaleValue<"space">
| Stitches.PropertyValue<"width" | "height">
) => ({
width: value,
height: value,
}),
@@ -279,45 +334,45 @@ export const {
// }),
// A property to apply linear gradient
linearGradient: (value: Stitches.ScaleValue<'space'>) => ({
linearGradient: (value: Stitches.ScaleValue<"space">) => ({
backgroundImage: `linear-gradient(${value})`,
}),
// An abbreviated property for border-radius
br: (value: Stitches.ScaleValue<'space'>) => ({
br: (value: Stitches.ScaleValue<"space">) => ({
borderRadius: value,
}),
},
});
export const darkTheme = createTheme('dark', {
export const darkTheme = createTheme("dark", {
colors: {
...grayDark,
...blueDark,
...redDark,
...greenDark,
...plumDark,
...crimsonDark,
...grassDark,
...slateDark,
...mauveDark,
...pinkDark,
...yellowDark,
...amberDark,
...purpleDark,
...redDark,
deep: "rgb(10, 10, 10)",
// backgroundA: transparentize(0.1, grayDark.gray1),
},
});
export const globalStyles = globalCss({
// body: { backgroundColor: '$background', color: '$text', fontFamily: 'Helvetica' },
'html, body': {
backgroundColor: '$gray1',
color: '$gray12',
fontFamily: '$body',
fontSize: '$md',
'-webkit-font-smoothing': 'antialiased',
'-moz-osx-font-smoothing': 'grayscale'
"html, body": {
backgroundColor: "$mauve2",
color: "$mauve12",
fontFamily: "$body",
fontSize: "$md",
"-webkit-font-smoothing": "antialiased",
"-moz-osx-font-smoothing": "grayscale",
},
a: {
color: "inherit",
textDecoration: "none",
},
'a': {
color: 'inherit',
textDecoration: 'none'
}
});

View File

@@ -6,8 +6,36 @@ body,
min-height: 100vh;
display: flex;
flex-direction: column;
overflow-y: hidden;
}
* {
box-sizing: border-box;
}
.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);
}

View File

@@ -3,7 +3,7 @@
"inherit": true,
"rules": [
{
"background": "1a1d1e",
"background": "161618",
"token": ""
},
{
@@ -182,10 +182,10 @@
],
"colors": {
"editor.foreground": "#D0D0FF",
"editor.background": "#232326",
"editor.background": "#1C1C1F",
"editor.selectionBackground": "#ffffff30",
"editor.lineHighlightBackground": "#ffffff20",
"editorCursor.foreground": "#7070FF",
"editorWhitespace.foreground": "#BFBFBF"
}
}
}

View File

@@ -3,7 +3,7 @@
"inherit": true,
"rules": [
{
"background": "FFFFFF",
"background": "F4F2F4",
"token": ""
},
{
@@ -89,10 +89,10 @@
],
"colors": {
"editor.foreground": "#000000",
"editor.background": "#f4f2f4",
"editor.background": "#F9F8F9",
"editor.selectionBackground": "#B5D5FF",
"editor.lineHighlightBackground": "#00000012",
"editorCursor.foreground": "#000000",
"editorWhitespace.foreground": "#BFBFBF"
}
}
}

207
yarn.lock
View File

@@ -50,6 +50,13 @@
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.12.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa"
integrity sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/types@7.15.0":
version "7.15.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.15.0.tgz#61af11f2286c4e9c69ca8deb5f4375a73c72dcbd"
@@ -76,6 +83,71 @@
vscode-languageserver-textdocument "^1.0.1"
vscode-uri "^3.0.2"
"@emotion/cache@^11.4.0", "@emotion/cache@^11.7.1":
version "11.7.1"
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.7.1.tgz#08d080e396a42e0037848214e8aa7bf879065539"
integrity sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==
dependencies:
"@emotion/memoize" "^0.7.4"
"@emotion/sheet" "^1.1.0"
"@emotion/utils" "^1.0.0"
"@emotion/weak-memoize" "^0.2.5"
stylis "4.0.13"
"@emotion/hash@^0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==
"@emotion/memoize@^0.7.4":
version "0.7.5"
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.5.tgz#2c40f81449a4e554e9fc6396910ed4843ec2be50"
integrity sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==
"@emotion/react@^11.1.1":
version "11.7.1"
resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.7.1.tgz#3f800ce9b20317c13e77b8489ac4a0b922b2fe07"
integrity sha512-DV2Xe3yhkF1yT4uAUoJcYL1AmrnO5SVsdfvu+fBuS7IbByDeTVx9+wFmvx9Idzv7/78+9Mgx2Hcmr7Fex3tIyw==
dependencies:
"@babel/runtime" "^7.13.10"
"@emotion/cache" "^11.7.1"
"@emotion/serialize" "^1.0.2"
"@emotion/sheet" "^1.1.0"
"@emotion/utils" "^1.0.0"
"@emotion/weak-memoize" "^0.2.5"
hoist-non-react-statics "^3.3.1"
"@emotion/serialize@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.0.2.tgz#77cb21a0571c9f68eb66087754a65fa97bfcd965"
integrity sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==
dependencies:
"@emotion/hash" "^0.8.0"
"@emotion/memoize" "^0.7.4"
"@emotion/unitless" "^0.7.5"
"@emotion/utils" "^1.0.0"
csstype "^3.0.2"
"@emotion/sheet@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.1.0.tgz#56d99c41f0a1cda2726a05aa6a20afd4c63e58d2"
integrity sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g==
"@emotion/unitless@^0.7.5":
version "0.7.5"
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed"
integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==
"@emotion/utils@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.0.0.tgz#abe06a83160b10570816c913990245813a2fd6af"
integrity sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA==
"@emotion/weak-memoize@^0.2.5":
version "0.2.5"
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46"
integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==
"@eslint/eslintrc@^0.4.3":
version "0.4.3"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c"
@@ -861,6 +933,22 @@
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11"
integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==
"@types/react-transition-group@^4.4.0":
version "4.4.4"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.4.tgz#acd4cceaa2be6b757db61ed7b432e103242d163e"
integrity sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==
dependencies:
"@types/react" "*"
"@types/react@*":
version "17.0.38"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.38.tgz#f24249fefd89357d5fa71f739a686b8d7c7202bd"
integrity sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/react@17.0.31":
version "17.0.31"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.31.tgz#fe05ebf91ff3ae35bb6b13f6c1b461db8089dff8"
@@ -1739,6 +1827,14 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
dom-helpers@^5.0.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==
dependencies:
"@babel/runtime" "^7.8.7"
csstype "^3.0.2"
domain-browser@4.19.0:
version "4.19.0"
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-4.19.0.tgz#1093e17c0a17dbd521182fe90d49ac1370054af1"
@@ -2177,6 +2273,11 @@ file-uri-to-path@1.0.0:
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
filesize@^8.0.7:
version "8.0.7"
resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.7.tgz#695e70d80f4e47012c132d57a059e80c6b580bd8"
integrity sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
@@ -2431,6 +2532,13 @@ hmac-drbg@^1.0.1:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
hoist-non-react-statics@^3.3.1:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
dependencies:
react-is "^16.7.0"
hotkeys-js@^3.8.1:
version "3.8.7"
resolved "https://registry.yarnpkg.com/hotkeys-js/-/hotkeys-js-3.8.7.tgz#c16cab978b53d7242f860ca3932e976b92399981"
@@ -2730,6 +2838,13 @@ isexe@^2.0.0:
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
javascript-time-ago@^2.3.11:
version "2.3.11"
resolved "https://registry.yarnpkg.com/javascript-time-ago/-/javascript-time-ago-2.3.11.tgz#b488ae765b7f42ff003f3830977a92411ef1fe92"
integrity sha512-R6Ux0IGv7RmAZ7vB04r/gevGaLDJeZY7+6jTYyYsPAF5x4PSv6up+dW2hLK99+OUY8xwdeXPItZFiUppIRUaoQ==
dependencies:
relative-time-format "^1.0.6"
jest-worker@27.0.0-next.5:
version "27.0.0-next.5"
resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.0.0-next.5.tgz#5985ee29b12a4e191f4aae4bb73b97971d86ec28"
@@ -2990,6 +3105,11 @@ md5.js@^1.3.4:
inherits "^2.0.1"
safe-buffer "^5.1.2"
memoize-one@^5.0.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
memoize-one@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045"
@@ -3500,6 +3620,11 @@ pbkdf2@^3.0.3, pbkdf2@^3.0.9:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
performance-now@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
phosphor-react@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/phosphor-react/-/phosphor-react-1.3.1.tgz#96e33f44370d83cda15b60cccc17087ad0060684"
@@ -3580,6 +3705,24 @@ progress@^2.0.0:
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
prop-types@^15.5.7:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
dependencies:
loose-envify "^1.4.0"
object-assign "^4.1.1"
react-is "^16.13.1"
prop-types@^15.6.0, prop-types@^15.6.2:
version "15.8.0"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.0.tgz#d237e624c45a9846e469f5f31117f970017ff588"
integrity sha512-fDGekdaHh65eI3lMi5OnErU6a8Ighg2KjcjQxO7m8VHyWjcPyj5kiOgV1LQDOOOgVy3+5FgjXvdSSX7B8/5/4g==
dependencies:
loose-envify "^1.4.0"
object-assign "^4.1.1"
react-is "^16.13.1"
prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
@@ -3628,6 +3771,13 @@ queue@6.0.2:
dependencies:
inherits "~2.0.3"
raf@^3.4.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==
dependencies:
performance-now "^2.1.0"
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
@@ -3689,7 +3839,7 @@ react-is@17.0.2:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
react-is@^16.8.1:
react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -3725,6 +3875,27 @@ react-remove-scroll@^2.4.0:
use-callback-ref "^1.2.3"
use-sidecar "^1.0.1"
react-select@^5.2.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.2.1.tgz#416c25c6b79b94687702374e019c4f2ed9d159d6"
integrity sha512-OOyNzfKrhOcw/BlembyGWgdlJ2ObZRaqmQppPFut1RptJO423j+Y+JIsmxkvsZ4D/3CpOmwIlCvWbbAWEdh12A==
dependencies:
"@babel/runtime" "^7.12.0"
"@emotion/cache" "^11.4.0"
"@emotion/react" "^11.1.1"
"@types/react-transition-group" "^4.4.0"
memoize-one "^5.0.0"
prop-types "^15.6.0"
react-transition-group "^4.3.0"
react-split@^2.0.14:
version "2.0.14"
resolved "https://registry.yarnpkg.com/react-split/-/react-split-2.0.14.tgz#ef198259bf43264d605f792fb3384f15f5b34432"
integrity sha512-bKWydgMgaKTg/2JGQnaJPg51T6dmumTWZppFgEbbY0Fbme0F5TuatAScCLaqommbGQQf/ZT1zaejuPDriscISA==
dependencies:
prop-types "^15.5.7"
split.js "^1.6.0"
react-stay-scrolled@^7.4.0:
version "7.4.0"
resolved "https://registry.yarnpkg.com/react-stay-scrolled/-/react-stay-scrolled-7.4.0.tgz#cb109b8dfd7834e5406d9c9322035ab40c398368"
@@ -3742,6 +3913,25 @@ react-style-singleton@^2.1.0:
invariant "^2.2.4"
tslib "^1.0.0"
react-time-ago@^7.1.9:
version "7.1.9"
resolved "https://registry.yarnpkg.com/react-time-ago/-/react-time-ago-7.1.9.tgz#df3999f1184b71ab09aaf1d05dda2a19b76e0bb8"
integrity sha512-jN4EsEgqm3a5eLJV0ZrRpom3iG+w4bullS2NHc2htIucGUIr2FbgHXjU0wIkuN9uWM87aFvIfkUDpS/ju7Mazg==
dependencies:
memoize-one "^6.0.0"
prop-types "^15.7.2"
raf "^3.4.1"
react-transition-group@^4.3.0:
version "4.4.2"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.2.tgz#8b59a56f09ced7b55cbd53c36768b922890d5470"
integrity sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==
dependencies:
"@babel/runtime" "^7.5.5"
dom-helpers "^5.0.1"
loose-envify "^1.4.0"
prop-types "^15.6.2"
react@17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
@@ -3812,6 +4002,11 @@ regexpp@^3.1.0:
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2"
integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==
relative-time-format@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/relative-time-format/-/relative-time-format-1.0.6.tgz#4956f6aa161f63cbab8a7f2d8b2e92d7b6a888a3"
integrity sha512-voemOJLxlKun4P1fAo4PEg2WXNGjhqfE/G8Xen4gcy24Hyu/djn5bT5axmhx4MnjynoZ8f0HCOjk3RZpsY6X/g==
require-from-string@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
@@ -4119,6 +4314,11 @@ source-map@^0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
split.js@^1.6.0:
version "1.6.5"
resolved "https://registry.yarnpkg.com/split.js/-/split.js-1.6.5.tgz#f7f61da1044c9984cb42947df4de4fadb5a3f300"
integrity sha512-mPTnGCiS/RiuTNsVhCm9De9cCAUsrNFFviRbADdKiiV+Kk8HKp/0fWu7Kr8pi3/yBmsqLFHuXGT9UUZ+CNLwFw==
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
@@ -4265,6 +4465,11 @@ stylis@3.5.4:
resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.4.tgz#f665f25f5e299cf3d64654ab949a57c768b73fbe"
integrity sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q==
stylis@4.0.13:
version "4.0.13"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91"
integrity sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"