Compare commits

...

34 Commits

Author SHA1 Message Date
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
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
41 changed files with 1034 additions and 343 deletions

View File

@@ -181,7 +181,11 @@ const AccountDialog = ({
target="_blank" target="_blank"
rel="noreferrer noopener" rel="noreferrer noopener"
> >
<Button size="sm" ghost css={{ color: "$green11 !important", mt: "$3" }}> <Button
size="sm"
ghost
css={{ color: "$grass11 !important", mt: "$3" }}
>
<ArrowSquareOut size="15px" /> <ArrowSquareOut size="15px" />
</Button> </Button>
</a> </a>
@@ -195,13 +199,7 @@ const AccountDialog = ({
fontFamily: "$monospace", fontFamily: "$monospace",
}} }}
> >
{activeAccount && activeAccount?.hooks?.length > 0 {activeAccount && activeAccount.hooks.length}
? activeAccount?.hooks
.map(i => {
return `${i?.substring(0, 6)}...${i?.substring(i.length - 4)}`;
})
.join(", ")
: ""}
</Text> </Text>
</Flex> </Flex>
</Flex> </Flex>
@@ -289,18 +287,19 @@ const Accounts: FC<AccountProps> = props => {
display: "flex", display: "flex",
backgroundColor: props.card ? "$deep" : "$mauve1", backgroundColor: props.card ? "$deep" : "$mauve1",
position: "relative", position: "relative",
width: "100%", flex: "1",
height: "100%", height: "100%",
flexShrink: 0, border: "1px solid $mauve6",
borderTop: "1px solid $mauve6",
borderRight: "1px solid $mauve6",
borderLeft: "1px solid $mauve6",
borderBottom: "1px solid $mauve6",
borderRadius: props.card ? "$md" : undefined, borderRadius: props.card ? "$md" : undefined,
}} }}
> >
<Container css={{ p: 0, flexShrink: 1, height: "100%" }}> <Container css={{ p: 0, flexShrink: 1, height: "100%" }}>
<Flex css={{ py: "$3", borderBottom: props.card ? "1px solid $mauve6" : undefined }}> <Flex
css={{
py: "$3",
borderBottom: props.card ? "1px solid $mauve6" : undefined,
}}
>
<Heading <Heading
as="h3" as="h3"
css={{ css={{
@@ -363,7 +362,12 @@ const Accounts: FC<AccountProps> = props => {
> >
<Box> <Box>
<Text>{account.name} </Text> <Text>{account.name} </Text>
<Text css={{ color: "$mauve9" }}> <Text
css={{
color: "$mauve9",
wordBreak: "break-word",
}}
>
{account.address} ( {account.address} (
{Dinero({ {Dinero({
amount: Number(account?.xrp || "0"), amount: Number(account?.xrp || "0"),
@@ -400,7 +404,7 @@ const Accounts: FC<AccountProps> = props => {
</Flex> </Flex>
{props.showHookStats && ( {props.showHookStats && (
<Text muted small css={{ mt: "$2" }}> <Text muted small css={{ mt: "$2" }}>
X hooks installed {account.hooks.length} hook{account.hooks.length === 1 ? "" : "s"} installed
</Text> </Text>
)} )}
</Flex> </Flex>

View File

@@ -91,26 +91,26 @@ export const StyledButton = styled("button", {
}, },
}, },
primary: { primary: {
backgroundColor: `$pink9`, backgroundColor: `$accent`,
boxShadow: "inset 0 0 0 1px $colors$pink9", boxShadow: "inset 0 0 0 1px $colors$purple9",
color: "$white", color: "$white",
"@hover": { "@hover": {
"&:hover": { "&:hover": {
backgroundColor: "$pink10", backgroundColor: "$purple10",
boxShadow: "inset 0 0 0 1px $colors$pink11", boxShadow: "inset 0 0 0 1px $colors$purple11",
}, },
}, },
"&:active": { "&:active": {
backgroundColor: "$pink8", backgroundColor: "$purple8",
boxShadow: "inset 0 0 0 1px $colors$pink8", boxShadow: "inset 0 0 0 1px $colors$purple8",
}, },
"&:focus": { "&:focus": {
boxShadow: "inset 0 0 0 2px $colors$pink12", boxShadow: "inset 0 0 0 2px $colors$purple12",
}, },
'&[data-radix-popover-trigger][data-state="open"], &[data-radix-dropdown-menu-trigger][data-state="open"]': '&[data-radix-popover-trigger][data-state="open"], &[data-radix-dropdown-menu-trigger][data-state="open"]':
{ {
backgroundColor: "$mauve4", backgroundColor: "$mauve4",
boxShadow: "inset 0 0 0 1px $colors$pink8", boxShadow: "inset 0 0 0 1px $colors$purple8",
}, },
}, },
secondary: { secondary: {

View File

@@ -34,7 +34,7 @@ const DeployEditor = () => {
display: "flex", display: "flex",
position: "relative", position: "relative",
flexDirection: "column", flexDirection: "column",
backgroundColor: "$mauve3", backgroundColor: "$mauve2",
width: "100%", width: "100%",
}} }}
> >

View File

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

View File

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

View File

@@ -29,7 +29,7 @@ loader.config({
const validateWritability = (editor: monaco.editor.IStandaloneCodeEditor) => { const validateWritability = (editor: monaco.editor.IStandaloneCodeEditor) => {
const currPath = editor.getModel()?.uri.path; const currPath = editor.getModel()?.uri.path;
if (apiHeaderFiles.find(h => currPath?.endsWith(h))) { if (apiHeaderFiles.find((h) => currPath?.endsWith(h))) {
editor.updateOptions({ readOnly: true }); editor.updateOptions({ readOnly: true });
} else { } else {
editor.updateOptions({ readOnly: false }); editor.updateOptions({ readOnly: false });
@@ -60,7 +60,7 @@ const HooksEditor = () => {
display: "flex", display: "flex",
position: "relative", position: "relative",
flexDirection: "column", flexDirection: "column",
backgroundColor: "$mauve3", backgroundColor: "$mauve2",
width: "100%", width: "100%",
}} }}
> >
@@ -71,15 +71,15 @@ const HooksEditor = () => {
keepCurrentModel keepCurrentModel
defaultLanguage={snap.files?.[snap.active]?.language} defaultLanguage={snap.files?.[snap.active]?.language}
language={snap.files?.[snap.active]?.language} language={snap.files?.[snap.active]?.language}
path={`file://work/c/${snap.files?.[snap.active]?.name}`} path={`file:///work/c/${snap.files?.[snap.active]?.name}`}
defaultValue={snap.files?.[snap.active]?.content} defaultValue={snap.files?.[snap.active]?.content}
beforeMount={monaco => { beforeMount={(monaco) => {
if (!snap.editorCtx) { if (!snap.editorCtx) {
snap.files.forEach(file => snap.files.forEach((file) =>
monaco.editor.createModel( monaco.editor.createModel(
file.content, file.content,
file.language, file.language,
monaco.Uri.parse(`file://work/c/${file.name}`) monaco.Uri.parse(`file:///work/c/${file.name}`)
) )
); );
} }
@@ -100,7 +100,7 @@ const HooksEditor = () => {
// listen when the web socket is opened // listen when the web socket is opened
listen({ listen({
webSocket: webSocket as WebSocket, webSocket: webSocket as WebSocket,
onConnection: connection => { onConnection: (connection) => {
// create and start the language client // create and start the language client
const languageClient = createLanguageClient(connection); const languageClient = createLanguageClient(connection);
const disposable = languageClient.start(); const disposable = languageClient.start();
@@ -139,10 +139,13 @@ const HooksEditor = () => {
enabled: true, enabled: true,
}, },
}); });
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => { editor.addCommand(
saveFile(); monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS,
}); () => {
validateWritability(editor) saveFile();
}
);
validateWritability(editor);
}} }}
theme={theme === "dark" ? "dark" : "light"} theme={theme === "dark" ? "dark" : "light"}
/> />
@@ -160,7 +163,9 @@ const HooksEditor = () => {
<Box css={{ display: "inline-flex", pl: "35px" }}> <Box css={{ display: "inline-flex", pl: "35px" }}>
<ArrowBendLeftUp size={30} /> <ArrowBendLeftUp size={30} />
</Box> </Box>
<Box css={{ pl: "0px", pt: "15px", flex: 1, display: "inline-flex" }}> <Box
css={{ pl: "0px", pt: "15px", flex: 1, display: "inline-flex" }}
>
<Text <Text
css={{ css={{
fontSize: "14px", fontSize: "14px",

View File

@@ -117,15 +117,17 @@ export const Input = styled("input", {
}, },
state: { state: {
invalid: { invalid: {
boxShadow: "inset 0 0 0 1px $colors$red7", boxShadow: "inset 0 0 0 1px $colors$crimson7",
"&:focus": { "&:focus": {
boxShadow: "inset 0px 0px 0px 1px $colors$red8, 0px 0px 0px 1px $colors$red8", boxShadow:
"inset 0px 0px 0px 1px $colors$crimson8, 0px 0px 0px 1px $colors$crimson8",
}, },
}, },
valid: { valid: {
boxShadow: "inset 0 0 0 1px $colors$green7", boxShadow: "inset 0 0 0 1px $colors$grass7",
"&:focus": { "&:focus": {
boxShadow: "inset 0px 0px 0px 1px $colors$green8, 0px 0px 0px 1px $colors$green8", boxShadow:
"inset 0px 0px 0px 1px $colors$grass8, 0px 0px 0px 1px $colors$grass8",
}, },
}, },
}, },

View File

@@ -21,7 +21,14 @@ interface ILogBox {
enhanced?: boolean; enhanced?: boolean;
} }
const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children, renderNav, enhanced }) => { const LogBox: React.FC<ILogBox> = ({
title,
clearLog,
logs,
children,
renderNav,
enhanced,
}) => {
const logRef = useRef<HTMLPreElement>(null); const logRef = useRef<HTMLPreElement>(null);
const { stayScrolled /*, scrollBottom*/ } = useStayScrolled(logRef); const { stayScrolled /*, scrollBottom*/ } = useStayScrolled(logRef);
@@ -38,10 +45,23 @@ const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children, renderNav,
background: "$mauve1", background: "$mauve1",
position: "relative", position: "relative",
flex: 1, flex: 1,
height: "100%",
}} }}
> >
<Container css={{ px: 0, flexShrink: 1 }}> <Container
<Flex css={{ py: "$3", alignItems: "center", fontSize: "$sm", fontWeight: 300 }}> css={{
px: 0,
height: "100%",
}}
>
<Flex
css={{
height: "48px",
alignItems: "center",
fontSize: "$sm",
fontWeight: 300,
}}
>
<Heading <Heading
as="h3" as="h3"
css={{ css={{
@@ -67,6 +87,7 @@ const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children, renderNav,
)} )}
</Flex> </Flex>
</Flex> </Flex>
<Box <Box
as="pre" as="pre"
ref={logRef} ref={logRef}
@@ -76,14 +97,14 @@ const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children, renderNav,
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
width: "100%", width: "100%",
height: "160px", height: "calc(100% - 48px)", // 100% minus the logbox header height
overflowY: "auto",
fontSize: "13px", fontSize: "13px",
fontWeight: "$body", fontWeight: "$body",
fontFamily: "$monospace", fontFamily: "$monospace",
px: "$3", px: "$3",
pb: "$2", pb: "$2",
whiteSpace: "normal", whiteSpace: "normal",
overflowY: "auto",
}} }}
> >
{logs?.map((log, index) => ( {logs?.map((log, index) => (
@@ -96,7 +117,7 @@ const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children, renderNav,
backgroundColor: enhanced ? "$backgroundAlt" : undefined, backgroundColor: enhanced ? "$backgroundAlt" : undefined,
}, },
}, },
p: "$2 $1", p: enhanced ? "$2 $1" : undefined,
}} }}
> >
<LogText variant={log.type}> <LogText variant={log.type}>

View File

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

View File

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

View File

@@ -27,7 +27,7 @@ import {
DialogTrigger, DialogTrigger,
} from "./Dialog"; } from "./Dialog";
import PanelBox from "./PanelBox"; import PanelBox from "./PanelBox";
import { templateFileIds } from '../state/constants'; import { templateFileIds } from "../state/constants";
const Navigation = () => { const Navigation = () => {
const router = useRouter(); const router = useRouter();
@@ -40,9 +40,11 @@ const Navigation = () => {
as="nav" as="nav"
css={{ css={{
display: "flex", display: "flex",
backgroundColor: "$mauve1",
borderBottom: "1px solid $mauve6", borderBottom: "1px solid $mauve6",
position: "relative", position: "relative",
zIndex: 2003, zIndex: 2003,
height: "60px",
}} }}
> >
<Container <Container
@@ -60,7 +62,7 @@ const Navigation = () => {
pr: "$4", pr: "$4",
}} }}
> >
<Link href="/" passHref> <Link href={gistId ? `/develop/${gistId}` : "/develop"} passHref>
<Box <Box
as="a" as="a"
css={{ css={{
@@ -69,7 +71,7 @@ const Navigation = () => {
color: "$textColor", color: "$textColor",
}} }}
> >
<Logo width="30px" height="30px" /> <Logo width="32px" height="32px" />
</Box> </Box>
</Link> </Link>
<Flex <Flex
@@ -83,20 +85,42 @@ const Navigation = () => {
<Spinner /> <Spinner />
) : ( ) : (
<> <>
<Heading css={{ lineHeight: 1 }}>{snap.files?.[0]?.name || "XRPL Hooks"}</Heading> <Heading css={{ lineHeight: 1 }}>
<Text css={{ fontSize: "$xs", color: "$mauve10", lineHeight: 1 }}> {snap.files?.[0]?.name || "XRPL Hooks"}
</Heading>
<Text
css={{ fontSize: "$xs", color: "$mauve10", lineHeight: 1 }}
>
{snap.files.length > 0 ? "Gist: " : "Playground"} {snap.files.length > 0 ? "Gist: " : "Playground"}
<Text css={{ color: "$mauve12" }}> {snap.files.length > 0 && (
{snap.files.length > 0 && <Link
`${snap.gistOwner || "-"}/${truncate(snap.gistId || "")}`} href={`https://gist.github.com/${snap.gistOwner || ""}/${
</Text> snap.gistId || ""
}`}
passHref
>
<Text
as="a"
target="_blank"
rel="noreferrer noopener"
css={{ color: "$mauve12" }}
>
{`${snap.gistOwner || "-"}/${truncate(
snap.gistId || ""
)}`}
</Text>
</Link>
)}
</Text> </Text>
</> </>
)} )}
</Flex> </Flex>
{router.isReady && ( {router.isReady && (
<ButtonGroup css={{ marginLeft: "auto" }}> <ButtonGroup css={{ marginLeft: "auto" }}>
<Dialog open={snap.mainModalOpen} onOpenChange={open => (state.mainModalOpen = open)}> <Dialog
open={snap.mainModalOpen}
onOpenChange={(open) => (state.mainModalOpen = open)}
>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button outline> <Button outline>
<FolderOpen size="15px" /> <FolderOpen size="15px" />
@@ -130,10 +154,12 @@ const Navigation = () => {
flexDirection: "column", flexDirection: "column",
p: "$7", p: "$7",
height: "100%", height: "100%",
backgroundColor: "$mauve2",
"@md": { "@md": {
width: "30%", width: "30%",
maxWidth: "300px",
borderBottom: "0px", borderBottom: "0px",
borderRight: "1px solid $colors$mauve5", borderRight: "1px solid $colors$mauve6",
}, },
}} }}
> >
@@ -144,9 +170,11 @@ const Navigation = () => {
alignItems: "center", alignItems: "center",
gap: "$3", gap: "$3",
fontSize: "$xl", fontSize: "$xl",
lineHeight: "$one",
fontWeight: "$bold",
}} }}
> >
<Logo width="30px" height="30px" /> XRPL Hooks Editor <Logo width="48px" height="48px" /> XRPL Hooks Builder
</DialogTitle> </DialogTitle>
<DialogDescription as="div"> <DialogDescription as="div">
<Text <Text
@@ -157,17 +185,20 @@ const Navigation = () => {
mb: "$7", mb: "$7",
}} }}
> >
Hooks add smart contract functionality to the XRP Ledger. Hooks add smart contract functionality to the XRP
Ledger.
</Text> </Text>
<Flex css={{ flexDirection: "column", gap: "$2", mt: "$2" }}> <Flex
css={{ flexDirection: "column", gap: "$2", mt: "$2" }}
>
<Text <Text
css={{ css={{
display: "inline-flex", display: "inline-flex",
alignItems: "center", alignItems: "center",
gap: "$3", gap: "$3",
color: "$green9", color: "$purple10",
"&:hover": { "&:hover": {
color: "$green11 !important", color: "$purple11",
}, },
"&:focus": { "&:focus": {
outline: 0, outline: 0,
@@ -178,7 +209,7 @@ const Navigation = () => {
target="_blank" target="_blank"
href="https://github.com/XRPL-Labs/xrpld-hooks" href="https://github.com/XRPL-Labs/xrpld-hooks"
> >
<ArrowUpRight size="15px" /> Developing Hooks <ArrowUpRight size="15px" /> Hooks Github
</Text> </Text>
<Text <Text
@@ -186,9 +217,9 @@ const Navigation = () => {
display: "inline-flex", display: "inline-flex",
alignItems: "center", alignItems: "center",
gap: "$3", gap: "$3",
color: "$green9", color: "$purple10",
"&:hover": { "&:hover": {
color: "$green11 !important", color: "$purple11",
}, },
"&:focus": { "&:focus": {
outline: 0, outline: 0,
@@ -206,9 +237,9 @@ const Navigation = () => {
display: "inline-flex", display: "inline-flex",
alignItems: "center", alignItems: "center",
gap: "$3", gap: "$3",
color: "$green9", color: "$purple10",
"&:hover": { "&:hover": {
color: "$green11 !important", color: "$purple11",
}, },
"&:focus": { "&:focus": {
outline: 0, outline: 0,
@@ -235,36 +266,51 @@ const Navigation = () => {
gap: "$3", gap: "$3",
alignItems: "flex-start", alignItems: "flex-start",
flexWrap: "wrap", flexWrap: "wrap",
backgroundImage: `url('/pattern.svg'), url('/pattern-2.svg')`, backgroundColor: "$mauve1",
backgroundRepeat: "no-repeat",
backgroundPosition: "bottom left, top right",
"@md": { "@md": {
gridTemplateColumns: "1fr 1fr 1fr", gridTemplateColumns: "1fr 1fr 1fr",
gridTemplateRows: "max-content", gridTemplateRows: "max-content",
}, },
}} }}
> >
<PanelBox as="a" href={`/develop/${templateFileIds.starter}`}> <PanelBox
as="a"
href={`/develop/${templateFileIds.starter}`}
>
<Heading>Starter</Heading> <Heading>Starter</Heading>
<Text>Just an empty starter with essential imports</Text> <Text>
Just a basic starter with essential imports
</Text>
</PanelBox> </PanelBox>
<PanelBox as="a" href={`/develop/${templateFileIds.starter}`}> <PanelBox
as="a"
href={`/develop/${templateFileIds.firewall}`}
>
<Heading>Firewall</Heading> <Heading>Firewall</Heading>
<Text>This Hook essentially checks a blacklist of accounts</Text> <Text>
This Hook essentially checks a blacklist of accounts
</Text>
</PanelBox> </PanelBox>
<PanelBox as="a" href={`/develop/${templateFileIds.accept}`}> <PanelBox
<Heading>Accept</Heading> as="a"
<Text>This hook just accepts any transaction coming through it</Text> href={`/develop/${templateFileIds.notary}`}
</PanelBox> >
<PanelBox as="a" href={`/develop/${templateFileIds.notary}`}>
<Heading>Notary</Heading> <Heading>Notary</Heading>
<Text>Collecting signatures for multi-sign transactions</Text> <Text>
Collecting signatures for multi-sign transactions
</Text>
</PanelBox> </PanelBox>
<PanelBox as="a" href={`/develop/${templateFileIds.carbon}`}> <PanelBox
as="a"
href={`/develop/${templateFileIds.carbon}`}
>
<Heading>Carbon</Heading> <Heading>Carbon</Heading>
<Text>Send a percentage of sum to an address</Text> <Text>Send a percentage of sum to an address</Text>
</PanelBox> </PanelBox>
<PanelBox as="a" href={`/develop/${templateFileIds.peggy}`}> <PanelBox
as="a"
href={`/develop/${templateFileIds.peggy}`}
>
<Heading>Peggy</Heading> <Heading>Peggy</Heading>
<Text>An oracle based stabe coin hook</Text> <Text>An oracle based stabe coin hook</Text>
</PanelBox> </PanelBox>
@@ -313,25 +359,53 @@ const Navigation = () => {
}} }}
> >
<ButtonGroup> <ButtonGroup>
<Link href={gistId ? `/develop/${gistId}` : "/develop"} passHref shallow> <Link
<Button as="a" outline={!router.pathname.includes("/develop")} uppercase> href={gistId ? `/develop/${gistId}` : "/develop"}
passHref
shallow
>
<Button
as="a"
outline={!router.pathname.includes("/develop")}
uppercase
>
Develop Develop
</Button> </Button>
</Link> </Link>
<Link href={gistId ? `/deploy/${gistId}` : "/deploy"} passHref shallow> <Link
<Button as="a" outline={!router.pathname.includes("/deploy")} uppercase> href={gistId ? `/deploy/${gistId}` : "/deploy"}
passHref
shallow
>
<Button
as="a"
outline={!router.pathname.includes("/deploy")}
uppercase
>
Deploy Deploy
</Button> </Button>
</Link> </Link>
<Link href={gistId ? `/test/${gistId}` : "/test"} passHref shallow> <Link
<Button as="a" outline={!router.pathname.includes("/test")} uppercase> href={gistId ? `/test/${gistId}` : "/test"}
passHref
shallow
>
<Button
as="a"
outline={!router.pathname.includes("/test")}
uppercase
>
Test Test
</Button> </Button>
</Link> </Link>
</ButtonGroup> </ButtonGroup>
<Button outline disabled> <Link href="https://xrpl-hooks.readme.io/" passHref>
<BookOpen size="15px" /> <a target="_blank" rel="noreferrer noopener">
</Button> <Button outline>
<BookOpen size="15px" />
</Button>
</a>
</Link>
</Stack> </Stack>
</Flex> </Flex>
</Container> </Container>

View File

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

View File

@@ -1,5 +1,5 @@
import { FC } from "react"; import { FC } from "react";
import { gray, grayDark } from "@radix-ui/colors"; import { mauve, mauveDark } from "@radix-ui/colors";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import { styled } from '../stitches.config'; import { styled } from '../stitches.config';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
@@ -11,11 +11,11 @@ const Select: FC<Props> = props => {
const isDark = theme === "dark"; const isDark = theme === "dark";
const colors: any = { const colors: any = {
// primary: pink.pink9, // primary: pink.pink9,
primary: isDark ? grayDark.gray4 : gray.gray4, primary: isDark ? mauveDark.mauve4 : mauve.mauve4,
secondary: isDark ? grayDark.gray8 : gray.gray8, secondary: isDark ? mauveDark.mauve8 : mauve.mauve8,
background: isDark ? "rgb(10, 10, 10)" : "rgb(244, 244, 244)", background: isDark ? "rgb(10, 10, 10)" : "rgb(245, 245, 245)",
searchText: isDark ? grayDark.gray12 : gray.gray12, searchText: isDark ? mauveDark.mauve12 : mauve.mauve12,
placeholder: isDark ? grayDark.gray11 : gray.gray11, placeholder: isDark ? mauveDark.mauve11 : mauve.mauve11,
}; };
colors.outline = colors.background; colors.outline = colors.background;
colors.selected = colors.secondary; colors.selected = colors.secondary;

View File

@@ -1,24 +1,66 @@
import { useEffect, useState } from "react"; import React, {
useEffect,
useState,
Fragment,
isValidElement,
useCallback,
} from "react";
import type { ReactNode, ReactElement } from "react"; import type { ReactNode, ReactElement } from "react";
import { Button, Stack } from "."; import { Box, Button, Flex, Input, Stack, Text } from ".";
import {
Dialog,
DialogTrigger,
DialogContent,
DialogTitle,
DialogDescription,
DialogClose,
} from "./Dialog";
import { Plus, X } from "phosphor-react";
import { styled } from "../stitches.config";
const ErrorText = styled(Text, {
color: "$crimson9",
mt: "$1",
display: "block",
});
interface TabProps { interface TabProps {
header?: string; header?: string;
children: ReactNode; children: ReactNode;
} }
// TODO customise strings shown
interface Props { interface Props {
activeIndex?: number; activeIndex?: number;
activeHeader?: string; activeHeader?: string;
headless?: boolean; headless?: boolean;
children: ReactElement<TabProps>[]; 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 Tab = (props: TabProps) => null;
export const Tabs = ({ children, activeIndex, activeHeader, headless }: Props) => { export const Tabs = ({
children,
activeIndex,
activeHeader,
headless,
keepAllAlive = false,
onCreateNewTab,
onCloseTab,
defaultExtension = "",
forceDefaultExtension,
}: Props) => {
const [active, setActive] = useState(activeIndex || 0); const [active, setActive] = useState(activeIndex || 0);
const tabProps: TabProps[] = children.map(elem => elem.props); const tabs: TabProps[] = children.map((elem) => elem.props);
const [isNewtabDialogOpen, setIsNewtabDialogOpen] = useState(false);
const [tabname, setTabname] = useState("");
const [newtabError, setNewtabError] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
if (activeIndex) setActive(activeIndex); if (activeIndex) setActive(activeIndex);
@@ -26,10 +68,57 @@ export const Tabs = ({ children, activeIndex, activeHeader, headless }: Props) =
useEffect(() => { useEffect(() => {
if (activeHeader) { if (activeHeader) {
const idx = tabProps.findIndex(tab => tab.header === activeHeader); const idx = tabs.findIndex((tab) => tab.header === activeHeader);
setActive(idx); setActive(idx);
} }
}, [activeHeader, tabProps]); }, [activeHeader, tabs]);
// when filename changes, reset error
useEffect(() => {
setNewtabError(null);
}, [tabname, setNewtabError]);
const validateTabname = useCallback(
(tabname: string): { error: string | null } => {
if (tabs.find((tab) => tab.header === tabname)) {
return { error: "Name already exists." };
}
return { error: null };
},
[tabs]
);
const handleCreateTab = useCallback(() => {
// add default extension in case omitted
let _tabname = tabname.includes(".") ? tabname : tabname + defaultExtension;
if (forceDefaultExtension && !_tabname.endsWith(defaultExtension)) {
_tabname = _tabname + defaultExtension;
}
const chk = validateTabname(_tabname);
if (chk.error) {
setNewtabError(`Error: ${chk.error}`);
return;
}
setIsNewtabDialogOpen(false);
setTabname("");
// switch to new tab?
setActive(tabs.length);
onCreateNewTab?.(_tabname);
}, [tabname, defaultExtension, validateTabname, onCreateNewTab, tabs.length]);
const handleCloseTab = useCallback(
(idx: number) => {
if (idx <= active && active !== 0) {
setActive(active - 1);
}
onCloseTab?.(idx, tabs[idx].header);
},
[active, onCloseTab, tabs]
);
return ( return (
<> <>
@@ -40,11 +129,13 @@ export const Tabs = ({ children, activeIndex, activeHeader, headless }: Props) =
flex: 1, flex: 1,
flexWrap: "nowrap", flexWrap: "nowrap",
marginBottom: "-1px", marginBottom: "-1px",
width: "100%",
overflow: "auto",
}} }}
> >
{tabProps.map((prop, idx) => ( {tabs.map((tab, idx) => (
<Button <Button
key={prop.header} key={tab.header}
role="tab" role="tab"
tabIndex={idx} tabIndex={idx}
onClick={() => setActive(idx)} onClick={() => setActive(idx)}
@@ -59,12 +150,108 @@ export const Tabs = ({ children, activeIndex, activeHeader, headless }: Props) =
}, },
}} }}
> >
{prop.header || idx} {tab.header || idx}
{onCloseTab && (
<Box
as="span"
css={{
display: "flex",
p: "2px",
borderRadius: "$full",
mr: "-4px",
"&:hover": {
// boxSizing: "0px 0px 1px",
backgroundColor: "$mauve2",
color: "$mauve12",
},
}}
onClick={(ev: React.MouseEvent<HTMLElement>) => {
ev.stopPropagation();
handleCloseTab(idx);
}}
>
<X size="9px" weight="bold" />
</Box>
)}
</Button> </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> </Stack>
)} )}
{tabProps[active].children} {keepAllAlive ? (
tabs.map((tab, idx) => {
// TODO Maybe rule out fragments as children
if (!isValidElement(tab.children)) {
if (active !== idx) return null;
return tab.children;
}
let key = tab.children.key || tab.header || idx;
let { children } = tab;
let { style, ...props } = children.props;
return (
<children.type
key={key}
{...props}
style={{ ...style, display: active !== idx ? "none" : undefined }}
/>
);
})
) : (
<Fragment key={tabs[active].header || active}>
{tabs[active].children}
</Fragment>
)}
</> </>
); );
}; };

View File

@@ -41,6 +41,7 @@
"react-hot-toast": "^2.1.1", "react-hot-toast": "^2.1.1",
"react-new-window": "^0.2.1", "react-new-window": "^0.2.1",
"react-select": "^5.2.1", "react-select": "^5.2.1",
"react-split": "^2.0.14",
"react-stay-scrolled": "^7.4.0", "react-stay-scrolled": "^7.4.0",
"reconnecting-websocket": "^4.4.0", "reconnecting-websocket": "^4.4.0",
"valtio": "^1.2.5", "valtio": "^1.2.5",

View File

@@ -18,6 +18,9 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
const slug = router.query?.slug; const slug = router.query?.slug;
const gistId = (Array.isArray(slug) && slug[0]) ?? null; const gistId = (Array.isArray(slug) && slug[0]) ?? null;
const origin = "https://xrpl-hooks-ide.vercel.app"; // TODO: Change when site is deployed
const shareImg = "/share-image.png";
useEffect(() => { useEffect(() => {
if (gistId && router.isReady) { if (gistId && router.isReady) {
fetchFiles(gistId); fetchFiles(gistId);
@@ -31,7 +34,73 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
return ( return (
<> <>
<Head> <Head>
<title>XRPL Hooks Playground</title> <meta charSet="utf-8" />
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<meta name="format-detection" content="telephone=no" />
<meta property="og:url" content={`${origin}${router.asPath}`} />
<title>XRPL Hooks 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> </Head>
<IdProvider> <IdProvider>
<SessionProvider session={session}> <SessionProvider session={session}>

View File

@@ -16,21 +16,10 @@ class MyDocument extends Document {
} }
render() { render() {
globalStyles(); globalStyles();
return ( return (
<Html> <Html>
<Head> <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 <style
id="stitches" id="stitches"
dangerouslySetInnerHTML={{ __html: getCssText() }} dangerouslySetInnerHTML={{ __html: getCssText() }}

View File

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

View File

@@ -2,6 +2,7 @@ import dynamic from "next/dynamic";
import { useSnapshot } from "valtio"; import { useSnapshot } from "valtio";
import Hotkeys from "react-hot-keys"; import Hotkeys from "react-hot-keys";
import { Play } from "phosphor-react"; import { Play } from "phosphor-react";
import Split from "react-split";
import type { NextPage } from "next"; import type { NextPage } from "next";
import { compileCode } from "../../state/actions"; import { compileCode } from "../../state/actions";
@@ -19,8 +20,16 @@ const LogBox = dynamic(() => import("../../components/LogBox"), {
const Home: NextPage = () => { const Home: NextPage = () => {
const snap = useSnapshot(state); const snap = useSnapshot(state);
return ( 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" }}> <main style={{ display: "flex", flex: 1, position: "relative" }}>
<HooksEditor /> <HooksEditor />
{snap.files[snap.active]?.name?.split(".")?.[1].toLowerCase() === {snap.files[snap.active]?.name?.split(".")?.[1].toLowerCase() ===
@@ -65,7 +74,7 @@ const Home: NextPage = () => {
logs={snap.logs} logs={snap.logs}
/> />
</Box> </Box>
</> </Split>
); );
}; };

View File

@@ -1,10 +1,21 @@
import { Container, Flex, Box, Tabs, Tab, Input, Select, Text, Button } from "../../components"; import {
Container,
Flex,
Box,
Tabs,
Tab,
Input,
Select,
Text,
Button,
} from "../../components";
import { Play } from "phosphor-react"; import { Play } from "phosphor-react";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import { useSnapshot } from "valtio"; import { useSnapshot } from "valtio";
import Split from "react-split";
import state from "../../state"; import state from "../../state";
import { sendTransaction } from "../../state/actions"; import { sendTransaction } from "../../state/actions";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState, FC } from "react";
import transactionsData from "../../content/transactions.json"; import transactionsData from "../../content/transactions.json";
const DebugStream = dynamic(() => import("../../components/DebugStream"), { const DebugStream = dynamic(() => import("../../components/DebugStream"), {
@@ -19,13 +30,20 @@ const Accounts = dynamic(() => import("../../components/Accounts"), {
}); });
// type SelectOption<T> = { value: T, label: string }; // type SelectOption<T> = { value: T, label: string };
type TxFields = Omit<typeof transactionsData[0], "Account" | "Sequence" | "TransactionType">; type TxFields = Omit<
typeof transactionsData[0],
"Account" | "Sequence" | "TransactionType"
>;
type OtherFields = (keyof Omit<TxFields, "Destination">)[]; type OtherFields = (keyof Omit<TxFields, "Destination">)[];
const Transaction = () => { interface Props {
header?: string;
}
const Transaction: FC<Props> = ({ header, ...props }) => {
const snap = useSnapshot(state); const snap = useSnapshot(state);
const transactionsOptions = transactionsData.map(tx => ({ const transactionsOptions = transactionsData.map((tx) => ({
value: tx.TransactionType, value: tx.TransactionType,
label: tx.TransactionType, label: tx.TransactionType,
})); }));
@@ -33,18 +51,20 @@ const Transaction = () => {
typeof transactionsOptions[0] | null typeof transactionsOptions[0] | null
>(null); >(null);
const accountOptions = snap.accounts.map(acc => ({ const accountOptions = snap.accounts.map((acc) => ({
label: acc.name, label: acc.name,
value: acc.address, value: acc.address,
})); }));
const [selectedAccount, setSelectedAccount] = useState<typeof accountOptions[0] | null>(null); const [selectedAccount, setSelectedAccount] = useState<
typeof accountOptions[0] | null
>(null);
const destAccountOptions = snap.accounts const destAccountOptions = snap.accounts
.map(acc => ({ .map((acc) => ({
label: acc.name, label: acc.name,
value: acc.address, value: acc.address,
})) }))
.filter(acc => acc.value !== selectedAccount?.value); .filter((acc) => acc.value !== selectedAccount?.value);
const [selectedDestAccount, setSelectedDestAccount] = useState< const [selectedDestAccount, setSelectedDestAccount] = useState<
typeof destAccountOptions[0] | null typeof destAccountOptions[0] | null
>(null); >(null);
@@ -55,7 +75,9 @@ const Transaction = () => {
useEffect(() => { useEffect(() => {
const transactionType = selectedTransaction?.value; const transactionType = selectedTransaction?.value;
const account = snap.accounts.find(acc => acc.address === selectedAccount?.value); const account = snap.accounts.find(
(acc) => acc.address === selectedAccount?.value
);
if (!account || !transactionType || txIsLoading) { if (!account || !transactionType || txIsLoading) {
setTxIsDisabled(true); setTxIsDisabled(true);
} else { } else {
@@ -65,7 +87,7 @@ const Transaction = () => {
useEffect(() => { useEffect(() => {
let _txFields: TxFields | undefined = transactionsData.find( let _txFields: TxFields | undefined = transactionsData.find(
tx => tx.TransactionType === selectedTransaction?.value (tx) => tx.TransactionType === selectedTransaction?.value
); );
if (!_txFields) return setTxFields({}); if (!_txFields) return setTxFields({});
_txFields = { ..._txFields } as TxFields; _txFields = { ..._txFields } as TxFields;
@@ -81,7 +103,9 @@ const Transaction = () => {
}, [selectedTransaction, setSelectedDestAccount]); }, [selectedTransaction, setSelectedDestAccount]);
const submitTest = useCallback(async () => { const submitTest = useCallback(async () => {
const account = snap.accounts.find(acc => acc.address === selectedAccount?.value); const account = snap.accounts.find(
(acc) => acc.address === selectedAccount?.value
);
const TransactionType = selectedTransaction?.value; const TransactionType = selectedTransaction?.value;
if (!account || !TransactionType || txIsDisabled) return; if (!account || !TransactionType || txIsDisabled) return;
@@ -91,7 +115,7 @@ const Transaction = () => {
let options = { ...txFields }; let options = { ...txFields };
options.Destination = selectedDestAccount?.value; options.Destination = selectedDestAccount?.value;
(Object.keys(options) as (keyof TxFields)[]).forEach(field => { (Object.keys(options) as (keyof TxFields)[]).forEach((field) => {
let _value = options[field]; let _value = options[field];
// convert currency // convert currency
if (typeof _value === "object" && _value.type === "currency") { if (typeof _value === "object" && _value.type === "currency") {
@@ -122,10 +146,15 @@ const Transaction = () => {
delete options[field]; delete options[field];
} }
}); });
await sendTransaction(account, { const logPrefix = header ? `${header.split(".")[0]}: ` : undefined;
TransactionType, await sendTransaction(
...options, account,
}); {
TransactionType,
...options,
},
{ logPrefix }
);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
if (error instanceof Error) { if (error instanceof Error) {
@@ -134,9 +163,10 @@ const Transaction = () => {
} }
setTxIsLoading(false); setTxIsLoading(false);
}, [ }, [
selectedAccount, header,
selectedDestAccount, selectedAccount?.value,
selectedTransaction, selectedDestAccount?.value,
selectedTransaction?.value,
snap.accounts, snap.accounts,
txFields, txFields,
txIsDisabled, txIsDisabled,
@@ -152,12 +182,20 @@ const Transaction = () => {
}, []); }, []);
const usualFields = ["TransactionType", "Amount", "Account", "Destination"]; const usualFields = ["TransactionType", "Amount", "Account", "Destination"];
const otherFields = Object.keys(txFields).filter(k => !usualFields.includes(k)) as OtherFields; const otherFields = Object.keys(txFields).filter(
(k) => !usualFields.includes(k)
) as OtherFields;
return ( return (
<Box css={{ position: "relative", height: "calc(100% - 28px)" }}> <Box css={{ position: "relative", height: "calc(100% - 28px)" }} {...props}>
<Container css={{ p: "$3 0", fontSize: "$sm", height: "calc(100% - 28px)" }}> <Container
css={{ p: "$3 0", fontSize: "$sm", height: "calc(100% - 28px)" }}
>
<Flex column fluid css={{ height: "100%", overflowY: "auto" }}> <Flex column fluid css={{ height: "100%", overflowY: "auto" }}>
<Flex row fluid css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}> <Flex
row
fluid
css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}
>
<Text muted css={{ mr: "$3" }}> <Text muted css={{ mr: "$3" }}>
Transaction type:{" "} Transaction type:{" "}
</Text> </Text>
@@ -168,10 +206,14 @@ const Transaction = () => {
hideSelectedOptions hideSelectedOptions
css={{ width: "70%" }} css={{ width: "70%" }}
value={selectedTransaction} value={selectedTransaction}
onChange={tt => setSelectedTransaction(tt as any)} onChange={(tt) => setSelectedTransaction(tt as any)}
/> />
</Flex> </Flex>
<Flex row fluid css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}> <Flex
row
fluid
css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}
>
<Text muted css={{ mr: "$3" }}> <Text muted css={{ mr: "$3" }}>
Account:{" "} Account:{" "}
</Text> </Text>
@@ -181,17 +223,25 @@ const Transaction = () => {
css={{ width: "70%" }} css={{ width: "70%" }}
options={accountOptions} options={accountOptions}
value={selectedAccount} value={selectedAccount}
onChange={acc => setSelectedAccount(acc as any)} onChange={(acc) => setSelectedAccount(acc as any)}
/> />
</Flex> </Flex>
{txFields.Amount !== undefined && ( {txFields.Amount !== undefined && (
<Flex row fluid css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}> <Flex
row
fluid
css={{
justifyContent: "flex-end",
alignItems: "center",
mb: "$3",
}}
>
<Text muted css={{ mr: "$3" }}> <Text muted css={{ mr: "$3" }}>
Amount (XRP):{" "} Amount (XRP):{" "}
</Text> </Text>
<Input <Input
value={txFields.Amount.value} value={txFields.Amount.value}
onChange={e => onChange={(e) =>
setTxFields({ setTxFields({
...txFields, ...txFields,
Amount: { type: "currency", value: e.target.value }, Amount: { type: "currency", value: e.target.value },
@@ -203,7 +253,15 @@ const Transaction = () => {
</Flex> </Flex>
)} )}
{txFields.Destination !== undefined && ( {txFields.Destination !== undefined && (
<Flex row fluid css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}> <Flex
row
fluid
css={{
justifyContent: "flex-end",
alignItems: "center",
mb: "$3",
}}
>
<Text muted css={{ mr: "$3" }}> <Text muted css={{ mr: "$3" }}>
Destination account:{" "} Destination account:{" "}
</Text> </Text>
@@ -214,28 +272,36 @@ const Transaction = () => {
options={destAccountOptions} options={destAccountOptions}
value={selectedDestAccount} value={selectedDestAccount}
isClearable isClearable
onChange={acc => setSelectedDestAccount(acc as any)} onChange={(acc) => setSelectedDestAccount(acc as any)}
/> />
</Flex> </Flex>
)} )}
{otherFields.map(field => { {otherFields.map((field) => {
let _value = txFields[field]; let _value = txFields[field];
let value = typeof _value === "object" ? _value.value : _value; let value = typeof _value === "object" ? _value.value : _value;
value = typeof value === "object" ? JSON.stringify(value) : value?.toLocaleString(); value =
let isCurrency = typeof _value === "object" && _value.type === "currency"; typeof value === "object"
? JSON.stringify(value)
: value?.toLocaleString();
let isCurrency =
typeof _value === "object" && _value.type === "currency";
return ( return (
<Flex <Flex
key={field} key={field}
row row
fluid fluid
css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }} css={{
justifyContent: "flex-end",
alignItems: "center",
mb: "$3",
}}
> >
<Text muted css={{ mr: "$3" }}> <Text muted css={{ mr: "$3" }}>
{field + (isCurrency ? " (XRP)" : "")}:{" "} {field + (isCurrency ? " (XRP)" : "")}:{" "}
</Text> </Text>
<Input <Input
value={value} value={value}
onChange={e => onChange={(e) =>
setTxFields({ setTxFields({
...txFields, ...txFields,
[field]: [field]:
@@ -284,41 +350,94 @@ const Transaction = () => {
const Test = () => { const Test = () => {
const snap = useSnapshot(state); const snap = useSnapshot(state);
const [tabHeaders, setTabHeaders] = useState<string[]>(["test1.json"]);
return ( return (
<Container css={{ py: "$3", px: 0 }}> <Container css={{ px: 0 }}>
<Flex <Split
row direction="vertical"
fluid sizes={[50, 50]}
css={{ justifyContent: "center", mb: "$2", height: "40vh", minHeight: "300px", p: "$3 $2" }} gutterSize={4}
gutterAlign="center"
style={{ height: "calc(100vh - 60px)" }}
> >
<Box css={{ width: "60%", px: "$2", maxWidth: "800px", height: "100%", overflow: "auto" }}> <Flex
<Tabs> row
{/* TODO Dynamic tabs */} fluid
<Tab header="test1.json"> css={{
<Transaction /> justifyContent: "center",
</Tab> p: "$3 $2",
<Tab header="test2.json"> }}
<Transaction /> >
</Tab> <Split
</Tabs> direction="horizontal"
</Box> sizes={[50, 50]}
<Box css={{ width: "40%", mx: "$2", height: "100%", maxWidth: "750px" }}> minSize={[180, 320]}
<Accounts card hideDeployBtn showHookStats /> gutterSize={4}
</Box> gutterAlign="center"
</Flex> 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 css={{ borderBottom: "1px solid $mauve8" }}> <Flex row fluid>
<Box css={{ width: "50%", borderRight: "1px solid $mauve8" }}> <Split
<LogBox direction="horizontal"
title="Development Log" sizes={[50, 50]}
logs={snap.transactionLogs} minSize={[320, 160]}
clearLog={() => (state.transactionLogs = [])} gutterSize={4}
/> gutterAlign="center"
</Box> style={{
<Box css={{ width: "50%" }}> display: "flex",
<DebugStream /> flexDirection: "row",
</Box> width: "100%",
</Flex> 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> </Container>
); );
}; };

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 // Set loading state to true
state.compiling = true; state.compiling = true;
state.logs = []
try { try {
const res = await fetch(process.env.NEXT_PUBLIC_COMPILE_API_ENDPOINT, { const res = await fetch(process.env.NEXT_PUBLIC_COMPILE_API_ENDPOINT, {
method: "POST", method: "POST",
@@ -38,7 +39,6 @@ export const compileCode = async (activeId: number) => {
{ {
type: "c", type: "c",
name: state.files[activeId].name, name: state.files[activeId].name,
options: "-O0",
src: state.files[activeId].content, src: state.files[activeId].content,
}, },
], ],
@@ -87,4 +87,4 @@ export const compileCode = async (activeId: number) => {
}); });
state.compiling = false; state.compiling = false;
} }
}; };

View File

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

View File

@@ -10,8 +10,11 @@ interface TransactionOptions {
Destination?: string Destination?: string
[index: string]: any [index: string]: any
} }
interface OtherOptions {
logPrefix?: string
}
export const sendTransaction = async (account: IAccount, txOptions: TransactionOptions) => { export const sendTransaction = async (account: IAccount, txOptions: TransactionOptions, options?: OtherOptions) => {
if (!state.client) throw Error('XRPL client not initalized') if (!state.client) throw Error('XRPL client not initalized')
const { Fee = "1000", ...opts } = txOptions const { Fee = "1000", ...opts } = txOptions
@@ -21,7 +24,7 @@ export const sendTransaction = async (account: IAccount, txOptions: TransactionO
Fee, // TODO auto-fillable Fee, // TODO auto-fillable
...opts ...opts
}; };
console.log({ tx }); const { logPrefix = '' } = options || {}
try { try {
const signedAccount = derive.familySeed(account.secret); const signedAccount = derive.familySeed(account.secret);
const { signedTransaction } = sign(tx, signedAccount); const { signedTransaction } = sign(tx, signedAccount);
@@ -32,19 +35,19 @@ export const sendTransaction = async (account: IAccount, txOptions: TransactionO
if (response.engine_result === "tesSUCCESS") { if (response.engine_result === "tesSUCCESS") {
state.transactionLogs.push({ state.transactionLogs.push({
type: 'success', type: 'success',
message: `Transaction success [${response.engine_result}]: ${response.engine_result_message}` message: `${logPrefix}[${response.engine_result}] ${response.engine_result_message}`
}) })
} else { } else {
state.transactionLogs.push({ state.transactionLogs.push({
type: "error", type: "error",
message: `[${response.error || response.engine_result}] ${response.error_exception || response.engine_result_message}`, message: `${logPrefix}[${response.error || response.engine_result}] ${response.error_exception || response.engine_result_message}`,
}); });
} }
} catch (err) { } catch (err) {
console.error(err); console.error(err);
state.transactionLogs.push({ state.transactionLogs.push({
type: "error", type: "error",
message: err instanceof Error ? `Error: ${err.message}` : 'Something went wrong, try again later', message: err instanceof Error ? `${logPrefix}Error: ${err.message}` : `${logPrefix}Something went wrong, try again later`,
}); });
} }
}; };

View File

@@ -1,6 +1,5 @@
export const templateFileIds = { export const templateFileIds = {
'starter': '1d14e51e2e02dc0a508cb0733767a914', // TODO currently same as accept 'starter': '1d14e51e2e02dc0a508cb0733767a914', // TODO currently same as accept
'accept': '1d14e51e2e02dc0a508cb0733767a914',
'firewall': 'bcd6d0c0fcbe52545ddb802481ff9d26', 'firewall': 'bcd6d0c0fcbe52545ddb802481ff9d26',
'notary': 'a789c75f591eeab7932fd702ed8cf9ea', 'notary': 'a789c75f591eeab7932fd702ed8cf9ea',
'carbon': '43925143fa19735d8c6505c34d3a6a47', 'carbon': '43925143fa19735d8c6505c34d3a6a47',

View File

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

View File

@@ -6,8 +6,36 @@ body,
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow-y: hidden;
} }
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
.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, "inherit": true,
"rules": [ "rules": [
{ {
"background": "1a1d1e", "background": "161618",
"token": "" "token": ""
}, },
{ {
@@ -182,10 +182,10 @@
], ],
"colors": { "colors": {
"editor.foreground": "#D0D0FF", "editor.foreground": "#D0D0FF",
"editor.background": "#232326", "editor.background": "#1C1C1F",
"editor.selectionBackground": "#ffffff30", "editor.selectionBackground": "#ffffff30",
"editor.lineHighlightBackground": "#ffffff20", "editor.lineHighlightBackground": "#ffffff20",
"editorCursor.foreground": "#7070FF", "editorCursor.foreground": "#7070FF",
"editorWhitespace.foreground": "#BFBFBF" "editorWhitespace.foreground": "#BFBFBF"
} }
} }

View File

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

View File

@@ -3688,6 +3688,15 @@ progress@^2.0.0:
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== 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: prop-types@^15.6.0, prop-types@^15.6.2:
version "15.8.0" version "15.8.0"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.0.tgz#d237e624c45a9846e469f5f31117f970017ff588" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.0.tgz#d237e624c45a9846e469f5f31117f970017ff588"
@@ -3855,6 +3864,14 @@ react-select@^5.2.1:
prop-types "^15.6.0" prop-types "^15.6.0"
react-transition-group "^4.3.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: react-stay-scrolled@^7.4.0:
version "7.4.0" version "7.4.0"
resolved "https://registry.yarnpkg.com/react-stay-scrolled/-/react-stay-scrolled-7.4.0.tgz#cb109b8dfd7834e5406d9c9322035ab40c398368" resolved "https://registry.yarnpkg.com/react-stay-scrolled/-/react-stay-scrolled-7.4.0.tgz#cb109b8dfd7834e5406d9c9322035ab40c398368"
@@ -4259,6 +4276,11 @@ source-map@^0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== 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: sprintf-js@~1.0.2:
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"