Run prettier through everything.

This commit is contained in:
muzam1l
2022-08-17 11:50:49 +05:30
parent 9923dd9390
commit 6418094b0f
110 changed files with 5458 additions and 5951 deletions

View File

@@ -35,3 +35,4 @@ yarn-error.log*
.vscode
*.md
utils/libwabt.js

View File

@@ -1,95 +1,93 @@
import toast from "react-hot-toast";
import { useSnapshot } from "valtio";
import { ArrowSquareOut, Copy, Trash, Wallet, X } from "phosphor-react";
import React, { useEffect, useState, FC } from "react";
import Dinero from "dinero.js";
import toast from 'react-hot-toast'
import { useSnapshot } from 'valtio'
import { ArrowSquareOut, Copy, Trash, Wallet, X } from 'phosphor-react'
import React, { useEffect, useState, FC } from 'react'
import Dinero from 'dinero.js'
import Button from "./Button";
import { addFaucetAccount, importAccount } from "../state/actions";
import state from "../state";
import Box from "./Box";
import { Container, Heading, Stack, Text, Flex } from ".";
import Button from './Button'
import { addFaucetAccount, importAccount } from '../state/actions'
import state from '../state'
import Box from './Box'
import { Container, Heading, Stack, Text, Flex } from '.'
import {
Dialog,
DialogContent,
DialogTitle,
DialogDescription,
DialogClose,
DialogTrigger,
} from "./Dialog";
import { css } from "../stitches.config";
import { Input, Label } from "./Input";
import truncate from "../utils/truncate";
DialogTrigger
} from './Dialog'
import { css } from '../stitches.config'
import { Input, Label } from './Input'
import truncate from '../utils/truncate'
const labelStyle = css({
color: "$mauve10",
textTransform: "uppercase",
fontSize: "10px",
mb: "$0.5",
});
import transactionsData from "../content/transactions.json";
import { SetHookDialog } from "./SetHookDialog";
import { addFunds } from "../state/actions/addFaucetAccount";
import { deleteHook } from "../state/actions/deployHook";
import { capitalize } from "../utils/helpers";
import { deleteAccount } from '../state/actions/deleteAccount';
color: '$mauve10',
textTransform: 'uppercase',
fontSize: '10px',
mb: '$0.5'
})
import transactionsData from '../content/transactions.json'
import { SetHookDialog } from './SetHookDialog'
import { addFunds } from '../state/actions/addFaucetAccount'
import { deleteHook } from '../state/actions/deployHook'
import { capitalize } from '../utils/helpers'
import { deleteAccount } from '../state/actions/deleteAccount'
export const AccountDialog = ({
activeAccountAddress,
setActiveAccountAddress,
setActiveAccountAddress
}: {
activeAccountAddress: string | null;
setActiveAccountAddress: React.Dispatch<React.SetStateAction<string | null>>;
activeAccountAddress: string | null
setActiveAccountAddress: React.Dispatch<React.SetStateAction<string | null>>
}) => {
const snap = useSnapshot(state);
const [showSecret, setShowSecret] = useState(false);
const activeAccount = snap.accounts.find(
account => account.address === activeAccountAddress
);
const snap = useSnapshot(state)
const [showSecret, setShowSecret] = useState(false)
const activeAccount = snap.accounts.find(account => account.address === activeAccountAddress)
return (
<Dialog
open={Boolean(activeAccountAddress)}
onOpenChange={open => {
setShowSecret(false);
!open && setActiveAccountAddress(null);
setShowSecret(false)
!open && setActiveAccountAddress(null)
}}
>
<DialogContent
css={{
backgroundColor: "$mauve1 !important",
border: "1px solid $mauve2",
".dark &": {
backgroundColor: '$mauve1 !important',
border: '1px solid $mauve2',
'.dark &': {
// backgroundColor: "$black !important",
},
p: "$3",
"&:before": {
content: " ",
position: "absolute",
p: '$3',
'&:before': {
content: ' ',
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
left: 0,
opacity: 0.2,
".dark &": {
opacity: 1,
'.dark &': {
opacity: 1
},
zIndex: 0,
pointerEvents: "none",
pointerEvents: 'none',
backgroundImage: `url('/pattern-dark.svg'), url('/pattern-dark-2.svg')`,
backgroundRepeat: "no-repeat",
backgroundPosition: "bottom left, top right",
},
backgroundRepeat: 'no-repeat',
backgroundPosition: 'bottom left, top right'
}
}}
>
<DialogTitle
css={{
display: "flex",
width: "100%",
alignItems: "center",
borderBottom: "1px solid $mauve6",
pb: "$3",
gap: "$3",
fontSize: "$md",
display: 'flex',
width: '100%',
alignItems: 'center',
borderBottom: '1px solid $mauve6',
pb: '$3',
gap: '$3',
fontSize: '$md'
}}
>
<Wallet size="15px" /> {activeAccount?.name}
@@ -97,25 +95,25 @@ export const AccountDialog = ({
<Button
size="xs"
outline
css={{ ml: "auto", mr: "$9" }}
css={{ ml: 'auto', mr: '$9' }}
tabIndex={-1}
onClick={() => {
deleteAccount(activeAccount?.address);
deleteAccount(activeAccount?.address)
}}
>
Delete Account <Trash size="15px" />
</Button>
</DialogClose>
</DialogTitle>
<DialogDescription as="div" css={{ fontFamily: "$monospace" }}>
<Stack css={{ display: "flex", flexDirection: "column", gap: "$3" }}>
<Flex css={{ alignItems: "center" }}>
<Flex css={{ flexDirection: "column" }}>
<DialogDescription as="div" css={{ fontFamily: '$monospace' }}>
<Stack css={{ display: 'flex', flexDirection: 'column', gap: '$3' }}>
<Flex css={{ alignItems: 'center' }}>
<Flex css={{ flexDirection: 'column' }}>
<Text className={labelStyle()}>Account Address</Text>
<Text
css={{
fontFamily: "$monospace",
a: { "&:hover": { textDecoration: "underline" } },
fontFamily: '$monospace',
a: { '&:hover': { textDecoration: 'underline' } }
}}
>
<a
@@ -127,94 +125,94 @@ export const AccountDialog = ({
</a>
</Text>
</Flex>
<Flex css={{ marginLeft: "auto", color: "$mauve12" }}>
<Flex css={{ marginLeft: 'auto', color: '$mauve12' }}>
<Button
size="sm"
ghost
css={{ mt: "$3" }}
css={{ mt: '$3' }}
onClick={() => {
navigator.clipboard.writeText(activeAccount?.address || "");
toast.success("Copied address to clipboard");
navigator.clipboard.writeText(activeAccount?.address || '')
toast.success('Copied address to clipboard')
}}
>
<Copy size="15px" />
</Button>
</Flex>
</Flex>
<Flex css={{ alignItems: "center" }}>
<Flex css={{ flexDirection: "column" }}>
<Flex css={{ alignItems: 'center' }}>
<Flex css={{ flexDirection: 'column' }}>
<Text className={labelStyle()}>Secret</Text>
<Text
as="div"
css={{
fontFamily: "$monospace",
display: "flex",
alignItems: "center",
fontFamily: '$monospace',
display: 'flex',
alignItems: 'center'
}}
>
{showSecret
? activeAccount?.secret
: "•".repeat(activeAccount?.secret.length || 16)}{" "}
: '•'.repeat(activeAccount?.secret.length || 16)}{' '}
<Button
css={{
fontFamily: "$monospace",
fontFamily: '$monospace',
lineHeight: 2,
mt: "2px",
ml: "$3",
mt: '2px',
ml: '$3'
}}
ghost
size="xs"
onClick={() => setShowSecret(curr => !curr)}
>
{showSecret ? "Hide" : "Show"}
{showSecret ? 'Hide' : 'Show'}
</Button>
</Text>
</Flex>
<Flex css={{ marginLeft: "auto", color: "$mauve12" }}>
<Flex css={{ marginLeft: 'auto', color: '$mauve12' }}>
<Button
size="sm"
ghost
onClick={() => {
navigator.clipboard.writeText(activeAccount?.secret || "");
toast.success("Copied secret to clipboard");
navigator.clipboard.writeText(activeAccount?.secret || '')
toast.success('Copied secret to clipboard')
}}
css={{ mt: "$3" }}
css={{ mt: '$3' }}
>
<Copy size="15px" />
</Button>
</Flex>
</Flex>
<Flex css={{ alignItems: "center" }}>
<Flex css={{ flexDirection: "column" }}>
<Flex css={{ alignItems: 'center' }}>
<Flex css={{ flexDirection: 'column' }}>
<Text className={labelStyle()}>Balances & Objects</Text>
<Text
css={{
fontFamily: "$monospace",
display: "flex",
alignItems: "center",
fontFamily: '$monospace',
display: 'flex',
alignItems: 'center'
}}
>
{Dinero({
amount: Number(activeAccount?.xrp || "0"),
precision: 6,
amount: Number(activeAccount?.xrp || '0'),
precision: 6
})
.toUnit()
.toLocaleString(undefined, {
style: "currency",
currency: "XRP",
currencyDisplay: "name",
style: 'currency',
currency: 'XRP',
currencyDisplay: 'name'
})}
<Button
css={{
fontFamily: "$monospace",
fontFamily: '$monospace',
lineHeight: 2,
mt: "2px",
ml: "$3",
mt: '2px',
ml: '$3'
}}
ghost
size="xs"
onClick={() => {
addFunds(activeAccount?.address || "");
addFunds(activeAccount?.address || '')
}}
>
Add Funds
@@ -223,7 +221,7 @@ export const AccountDialog = ({
</Flex>
<Flex
css={{
marginLeft: "auto",
marginLeft: 'auto'
}}
>
<a
@@ -231,23 +229,19 @@ export const AccountDialog = ({
target="_blank"
rel="noreferrer noopener"
>
<Button
size="sm"
ghost
css={{ color: "$grass11 !important", mt: "$3" }}
>
<Button size="sm" ghost css={{ color: '$grass11 !important', mt: '$3' }}>
<ArrowSquareOut size="15px" />
</Button>
</a>
</Flex>
</Flex>
<Flex css={{ alignItems: "center" }}>
<Flex css={{ flexDirection: "column" }}>
<Flex css={{ alignItems: 'center' }}>
<Flex css={{ flexDirection: 'column' }}>
<Text className={labelStyle()}>Installed Hooks</Text>
<Text
css={{
fontFamily: "$monospace",
a: { "&:hover": { textDecoration: "underline" } },
fontFamily: '$monospace',
a: { '&:hover': { textDecoration: 'underline' } }
}}
>
{activeAccount && activeAccount.hooks.length > 0
@@ -261,20 +255,20 @@ export const AccountDialog = ({
>
{truncate(i, 12)}
</a>
);
)
})
: ""}
: ''}
</Text>
</Flex>
{activeAccount && activeAccount?.hooks?.length > 0 && (
<Flex css={{ marginLeft: "auto" }}>
<Flex css={{ marginLeft: 'auto' }}>
<Button
size="xs"
outline
disabled={activeAccount.isLoading}
css={{ mt: "$3", mr: "$1", ml: "auto" }}
css={{ mt: '$3', mr: '$1', ml: 'auto' }}
onClick={() => {
deleteHook(activeAccount);
deleteHook(activeAccount)
}}
>
Delete Hook <Trash size="15px" />
@@ -285,117 +279,109 @@ export const AccountDialog = ({
</Stack>
</DialogDescription>
<DialogClose asChild>
<Box css={{ position: "absolute", top: "$3", right: "$3" }}>
<Box css={{ position: 'absolute', top: '$3', right: '$3' }}>
<X size="20px" />
</Box>
</DialogClose>
</DialogContent>
</Dialog>
);
};
)
}
interface AccountProps {
card?: boolean;
hideDeployBtn?: boolean;
showHookStats?: boolean;
card?: boolean
hideDeployBtn?: boolean
showHookStats?: boolean
}
const Accounts: FC<AccountProps> = props => {
const snap = useSnapshot(state);
const [activeAccountAddress, setActiveAccountAddress] = useState<
string | null
>(null);
const snap = useSnapshot(state)
const [activeAccountAddress, setActiveAccountAddress] = useState<string | null>(null)
useEffect(() => {
const fetchAccInfo = async () => {
if (snap.clientStatus === "online") {
if (snap.clientStatus === 'online') {
const requests = snap.accounts.map(acc =>
snap.client?.send({
id: `hooks-builder-req-info-${acc.address}`,
command: "account_info",
account: acc.address,
command: 'account_info',
account: acc.address
})
);
const responses = await Promise.all(requests);
)
const responses = await Promise.all(requests)
responses.forEach((res: any) => {
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 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)
if (accountToUpdate) {
accountToUpdate.xrp = balance;
accountToUpdate.sequence = sequence;
accountToUpdate.error = null;
accountToUpdate.xrp = balance
accountToUpdate.sequence = sequence
accountToUpdate.error = null
} else {
const oldAccount = state.accounts.find(
acc => acc.address === res?.account
);
const oldAccount = state.accounts.find(acc => acc.address === res?.account)
if (oldAccount) {
oldAccount.xrp = "0";
oldAccount.xrp = '0'
oldAccount.error = {
code: res?.error,
message: res?.error_message,
};
message: res?.error_message
}
}
});
}
})
const objectRequests = snap.accounts.map(acc => {
return snap.client?.send({
id: `hooks-builder-req-objects-${acc.address}`,
command: "account_objects",
account: acc.address,
});
});
const objectResponses = await Promise.all(objectRequests);
command: 'account_objects',
account: acc.address
})
})
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 address = res?.account as string
const accountToUpdate = state.accounts.find(acc => acc.address === address)
if (accountToUpdate) {
accountToUpdate.hooks =
res.account_objects
.find((ac: any) => ac?.LedgerEntryType === "Hook")
?.Hooks?.map((oo: any) => oo.Hook.HookHash) || [];
.find((ac: any) => ac?.LedgerEntryType === 'Hook')
?.Hooks?.map((oo: any) => oo.Hook.HookHash) || []
}
})
}
});
}
};
let fetchAccountInfoInterval: NodeJS.Timer;
if (snap.clientStatus === "online") {
fetchAccInfo();
fetchAccountInfoInterval = setInterval(() => fetchAccInfo(), 10000);
let fetchAccountInfoInterval: NodeJS.Timer
if (snap.clientStatus === 'online') {
fetchAccInfo()
fetchAccountInfoInterval = setInterval(() => fetchAccInfo(), 10000)
}
return () => {
if (snap.accounts.length > 0) {
if (fetchAccountInfoInterval) {
clearInterval(fetchAccountInfoInterval);
clearInterval(fetchAccountInfoInterval)
}
}
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [snap.accounts.length, snap.clientStatus]);
}, [snap.accounts.length, snap.clientStatus])
return (
<Box
as="div"
css={{
display: "flex",
backgroundColor: props.card ? "$deep" : "$mauve1",
position: "relative",
flex: "1",
height: "100%",
border: "1px solid $mauve6",
borderRadius: props.card ? "$md" : undefined,
display: 'flex',
backgroundColor: props.card ? '$deep' : '$mauve1',
position: 'relative',
flex: '1',
height: '100%',
border: '1px solid $mauve6',
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,
py: '$3',
borderBottom: props.card ? '1px solid $mauve6' : undefined
}}
>
<Heading
@@ -403,33 +389,33 @@ const Accounts: FC<AccountProps> = props => {
css={{
fontWeight: 300,
m: 0,
fontSize: "11px",
color: "$mauve12",
px: "$3",
textTransform: "uppercase",
alignItems: "center",
display: "inline-flex",
gap: "$3",
fontSize: '11px',
color: '$mauve12',
px: '$3',
textTransform: 'uppercase',
alignItems: 'center',
display: 'inline-flex',
gap: '$3'
}}
>
<Wallet size="15px" /> <Text css={{ lineHeight: 1 }}>Accounts</Text>
</Heading>
<Flex css={{ ml: "auto", gap: "$3", marginRight: "$3" }}>
<Flex css={{ ml: 'auto', gap: '$3', marginRight: '$3' }}>
<ImportAccountDialog type="create" />
<ImportAccountDialog />
</Flex>
</Flex>
<Stack
css={{
flexDirection: "column",
width: "100%",
fontSize: "13px",
wordWrap: "break-word",
fontWeight: "$body",
flexDirection: 'column',
width: '100%',
fontSize: '13px',
wordWrap: 'break-word',
fontWeight: '$body',
gap: 0,
height: "calc(100% - 52px)",
flexWrap: "nowrap",
overflowY: "auto",
height: 'calc(100% - 52px)',
flexWrap: 'nowrap',
overflowY: 'auto'
}}
>
{snap.accounts.map(account => (
@@ -438,45 +424,45 @@ const Accounts: FC<AccountProps> = props => {
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",
},
},
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",
justifyContent: 'space-between'
}}
>
<Box>
<Text>{account.name} </Text>
<Text
css={{
color: "$textMuted",
wordBreak: "break-word",
color: '$textMuted',
wordBreak: 'break-word'
}}
>
{account.address}{" "}
{account.address}{' '}
{!account?.error ? (
`(${Dinero({
amount: Number(account?.xrp || "0"),
precision: 6,
amount: Number(account?.xrp || '0'),
precision: 6
})
.toUnit()
.toLocaleString(undefined, {
style: "currency",
currency: "XRP",
currencyDisplay: "name",
style: 'currency',
currency: 'XRP',
currencyDisplay: 'name'
})})`
) : (
<Box css={{ color: "$red11" }}>
<Box css={{ color: '$red11' }}>
(Account not found, request funds to activate account)
</Box>
)}
@@ -486,7 +472,7 @@ const Accounts: FC<AccountProps> = props => {
<div
className="hook-deploy-button"
onClick={e => {
e.stopPropagation();
e.stopPropagation()
}}
>
<SetHookDialog accountAddress={account.address} />
@@ -494,9 +480,9 @@ const Accounts: FC<AccountProps> = props => {
)}
</Flex>
{props.showHookStats && (
<Text muted small css={{ mt: "$2" }}>
<Text muted small css={{ mt: '$2' }}>
{account.hooks.length} hook
{account.hooks.length === 1 ? "" : "s"} installed
{account.hooks.length === 1 ? '' : 's'} installed
</Text>
)}
</Flex>
@@ -508,37 +494,33 @@ const Accounts: FC<AccountProps> = props => {
setActiveAccountAddress={setActiveAccountAddress}
/>
</Box>
);
};
)
}
export const transactionsOptions = transactionsData.map(tx => ({
value: tx.TransactionType,
label: tx.TransactionType,
}));
label: tx.TransactionType
}))
const ImportAccountDialog = ({
type = "import",
}: {
type?: "import" | "create";
}) => {
const [secret, setSecret] = useState("");
const [name, setName] = useState("");
const ImportAccountDialog = ({ type = 'import' }: { type?: 'import' | 'create' }) => {
const [secret, setSecret] = useState('')
const [name, setName] = useState('')
const btnText = type === "import" ? "Import" : "Create";
const title = type === "import" ? "Import Account" : "Create Account";
const btnText = type === 'import' ? 'Import' : 'Create'
const title = type === 'import' ? 'Import Account' : 'Create Account'
const handleSubmit = async () => {
if (type === "create") {
const value = capitalize(name);
await addFaucetAccount(value, true);
setName("");
setSecret("");
return;
if (type === 'create') {
const value = capitalize(name)
await addFaucetAccount(value, true)
setName('')
setSecret('')
return
}
importAccount(secret, name)
setName('')
setSecret('')
}
importAccount(secret, name);
setName("");
setSecret("");
};
return (
<Dialog>
<DialogTrigger asChild>
@@ -547,9 +529,9 @@ const ImportAccountDialog = ({
</Button>
</DialogTrigger>
<DialogContent aria-describedby={undefined}>
<DialogTitle css={{ mb: "$4" }}>{title}</DialogTitle>
<DialogTitle css={{ mb: '$4' }}>{title}</DialogTitle>
<Flex column>
<Box css={{ mb: "$2" }}>
<Box css={{ mb: '$2' }}>
<Label>
Account name <Text muted>(optional)</Text>
</Label>
@@ -562,7 +544,7 @@ const ImportAccountDialog = ({
onChange={e => setName(e.target.value)}
/>
</Box>
{type === "import" && (
{type === 'import' && (
<Box>
<Label>Account secret</Label>
<Input
@@ -580,8 +562,8 @@ const ImportAccountDialog = ({
<Flex
css={{
marginTop: 25,
justifyContent: "flex-end",
gap: "$3",
justifyContent: 'flex-end',
gap: '$3'
}}
>
<DialogClose asChild>
@@ -594,13 +576,13 @@ const ImportAccountDialog = ({
</DialogClose>
</Flex>
<DialogClose asChild>
<Box css={{ position: "absolute", top: "$3", right: "$3" }}>
<Box css={{ position: 'absolute', top: '$3', right: '$3' }}>
<X size="20px" />
</Box>
</DialogClose>
</DialogContent>
</Dialog>
);
};
)
}
export default Accounts;
export default Accounts

View File

@@ -1,65 +1,62 @@
import { FC, ReactNode } from "react";
import { proxy, useSnapshot } from "valtio";
import Button from "../Button";
import Flex from "../Flex";
import { FC, ReactNode } from 'react'
import { proxy, useSnapshot } from 'valtio'
import Button from '../Button'
import Flex from '../Flex'
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogTitle,
} from "./primitive";
AlertDialogTitle
} from './primitive'
export interface AlertState {
isOpen: boolean;
title?: string;
body?: ReactNode;
cancelText?: string;
confirmText?: string;
confirmPrefix?: ReactNode;
onConfirm?: () => any;
onCancel?: () => any;
isOpen: boolean
title?: string
body?: ReactNode
cancelText?: string
confirmText?: string
confirmPrefix?: ReactNode
onConfirm?: () => any
onCancel?: () => any
}
export const alertState = proxy<AlertState>({
isOpen: false,
});
isOpen: false
})
const Alert: FC = () => {
const {
title = "Are you sure?",
title = 'Are you sure?',
isOpen,
body,
cancelText,
confirmText = "Ok",
confirmText = 'Ok',
confirmPrefix,
onCancel,
onConfirm,
} = useSnapshot(alertState);
onConfirm
} = useSnapshot(alertState)
return (
<AlertDialog
open={isOpen}
onOpenChange={value => (alertState.isOpen = value)}
>
<AlertDialog open={isOpen} onOpenChange={value => (alertState.isOpen = value)}>
<AlertDialogContent>
<AlertDialogTitle>{title}</AlertDialogTitle>
<AlertDialogDescription>{body}</AlertDialogDescription>
<Flex css={{ justifyContent: "flex-end", gap: "$3" }}>
<Flex css={{ justifyContent: 'flex-end', gap: '$3' }}>
{(cancelText || onCancel) && (
<AlertDialogCancel asChild>
<Button css={{ minWidth: "$16" }} outline onClick={onCancel}>
{cancelText || "Cancel"}
<Button css={{ minWidth: '$16' }} outline onClick={onCancel}>
{cancelText || 'Cancel'}
</Button>
</AlertDialogCancel>
)}
<AlertDialogAction asChild>
<Button
css={{ minWidth: "$16" }}
css={{ minWidth: '$16' }}
variant="primary"
onClick={async () => {
await onConfirm?.();
alertState.isOpen = false;
await onConfirm?.()
alertState.isOpen = false
}}
>
{confirmPrefix}
@@ -69,7 +66,7 @@ const Alert: FC = () => {
</Flex>
</AlertDialogContent>
</AlertDialog>
);
};
)
}
export default Alert;
export default Alert

View File

@@ -1,88 +1,83 @@
import React from "react";
import { blackA } from "@radix-ui/colors";
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
import { styled, keyframes } from "../../stitches.config";
import React from 'react'
import { blackA } from '@radix-ui/colors'
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
import { styled, keyframes } from '../../stitches.config'
const overlayShow = keyframes({
"0%": { opacity: 0 },
"100%": { opacity: 1 },
});
'0%': { opacity: 0 },
'100%': { opacity: 1 }
})
const contentShow = keyframes({
"0%": { opacity: 0, transform: "translate(-50%, -48%) scale(.96)" },
"100%": { opacity: 1, transform: "translate(-50%, -50%) scale(1)" },
});
'0%': { opacity: 0, transform: 'translate(-50%, -48%) scale(.96)' },
'100%': { opacity: 1, transform: 'translate(-50%, -50%) scale(1)' }
})
const StyledOverlay = styled(AlertDialogPrimitive.Overlay, {
zIndex: 1000,
backgroundColor: blackA.blackA9,
position: "fixed",
position: 'fixed',
inset: 0,
"@media (prefers-reduced-motion: no-preference)": {
animation: `${overlayShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
'@media (prefers-reduced-motion: no-preference)': {
animation: `${overlayShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`
},
".dark &": {
backgroundColor: blackA.blackA11,
},
});
'.dark &': {
backgroundColor: blackA.blackA11
}
})
const Root: React.FC<AlertDialogPrimitive.AlertDialogProps> = ({
children,
...rest
}) => {
const Root: React.FC<AlertDialogPrimitive.AlertDialogProps> = ({ children, ...rest }) => {
return (
<AlertDialogPrimitive.Root {...rest}>
<StyledOverlay />
{children}
</AlertDialogPrimitive.Root>
);
};
)
}
const StyledContent = styled(AlertDialogPrimitive.Content, {
zIndex: 1000,
backgroundColor: "$mauve2",
color: "$mauve12",
borderRadius: "$md",
boxShadow:
"0px 10px 38px -5px rgba(22, 23, 24, 0.25), 0px 10px 20px -5px rgba(22, 23, 24, 0.2)",
position: "fixed",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: "90vw",
maxWidth: "450px",
maxHeight: "85vh",
backgroundColor: '$mauve2',
color: '$mauve12',
borderRadius: '$md',
boxShadow: '0px 10px 38px -5px rgba(22, 23, 24, 0.25), 0px 10px 20px -5px rgba(22, 23, 24, 0.2)',
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: '90vw',
maxWidth: '450px',
maxHeight: '85vh',
padding: 25,
"@media (prefers-reduced-motion: no-preference)": {
animation: `${contentShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
'@media (prefers-reduced-motion: no-preference)': {
animation: `${contentShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`
},
"&:focus": { outline: "none" },
".dark &": {
backgroundColor: "$mauve5",
boxShadow:
"0px 10px 38px 0px rgba(0, 0, 0, 0.85), 0px 10px 20px 0px rgba(0, 0, 0, 0.6)",
},
});
'&:focus': { outline: 'none' },
'.dark &': {
backgroundColor: '$mauve5',
boxShadow: '0px 10px 38px 0px rgba(0, 0, 0, 0.85), 0px 10px 20px 0px rgba(0, 0, 0, 0.6)'
}
})
const StyledTitle = styled(AlertDialogPrimitive.Title, {
margin: 0,
color: "$mauve12",
color: '$mauve12',
fontWeight: 500,
fontSize: "$lg",
});
fontSize: '$lg'
})
const StyledDescription = styled(AlertDialogPrimitive.Description, {
marginBottom: 20,
color: "$mauve11",
color: '$mauve11',
lineHeight: 1.5,
fontSize: "$md",
});
fontSize: '$md'
})
// Exports
export const AlertDialog = Root;
export const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
export const AlertDialogContent = StyledContent;
export const AlertDialogTitle = StyledTitle;
export const AlertDialogDescription = StyledDescription;
export const AlertDialogAction = AlertDialogPrimitive.Action;
export const AlertDialogCancel = AlertDialogPrimitive.Cancel;
export const AlertDialog = Root
export const AlertDialogTrigger = AlertDialogPrimitive.Trigger
export const AlertDialogContent = StyledContent
export const AlertDialogTitle = StyledTitle
export const AlertDialogDescription = StyledDescription
export const AlertDialogAction = AlertDialogPrimitive.Action
export const AlertDialogCancel = AlertDialogPrimitive.Cancel

View File

@@ -1,8 +1,8 @@
import { styled } from "../stitches.config";
import { styled } from '../stitches.config'
const Box = styled("div", {
const Box = styled('div', {
// all: "unset",
boxSizing: "border-box",
});
boxSizing: 'border-box'
})
export default Box;
export default Box

View File

@@ -1,291 +1,285 @@
import React from "react";
import { styled } from "../stitches.config";
import Flex from "./Flex";
import Spinner from "./Spinner";
import React from 'react'
import { styled } from '../stitches.config'
import Flex from './Flex'
import Spinner from './Spinner'
export const StyledButton = styled("button", {
export const StyledButton = styled('button', {
// Reset
all: "unset",
position: "relative",
appereance: "none",
fontFamily: "$body",
alignItems: "center",
boxSizing: "border-box",
userSelect: "none",
"&::before": {
boxSizing: "border-box",
all: 'unset',
position: 'relative',
appereance: 'none',
fontFamily: '$body',
alignItems: 'center',
boxSizing: 'border-box',
userSelect: 'none',
'&::before': {
boxSizing: 'border-box'
},
"&::after": {
boxSizing: "border-box",
'&::after': {
boxSizing: 'border-box'
},
// Custom reset?
display: "inline-flex",
display: 'inline-flex',
flexShrink: 0,
justifyContent: "center",
lineHeight: "1",
gap: "5px",
WebkitTapHighlightColor: "rgba(0,0,0,0)",
justifyContent: 'center',
lineHeight: '1',
gap: '5px',
WebkitTapHighlightColor: 'rgba(0,0,0,0)',
// Custom
height: "$6",
px: "$2",
fontSize: "$2",
height: '$6',
px: '$2',
fontSize: '$2',
fontWeight: 500,
fontVariantNumeric: "tabular-nums",
cursor: "pointer",
width: "max-content",
"&:disabled": {
fontVariantNumeric: 'tabular-nums',
cursor: 'pointer',
width: 'max-content',
'&:disabled': {
opacity: 0.6,
pointerEvents: "none",
cursor: "not-allowed",
pointerEvents: 'none',
cursor: 'not-allowed'
},
variants: {
size: {
xs: {
borderRadius: "$sm",
height: "$5",
px: "$2",
fontSize: "$xs",
borderRadius: '$sm',
height: '$5',
px: '$2',
fontSize: '$xs'
},
sm: {
borderRadius: "$sm",
height: "$7",
px: "$3",
fontSize: "$xs",
borderRadius: '$sm',
height: '$7',
px: '$3',
fontSize: '$xs'
},
md: {
borderRadius: "$sm",
height: "$8",
px: "$3",
fontSize: "$xs",
borderRadius: '$sm',
height: '$8',
px: '$3',
fontSize: '$xs'
},
lg: {
borderRadius: "$sm",
height: "$10",
px: "$4",
fontSize: "$xs",
},
borderRadius: '$sm',
height: '$10',
px: '$4',
fontSize: '$xs'
}
},
variant: {
link: {
textDecoration: "underline",
fontSize: "inherit",
color: "$textMuted",
textUnderlineOffset: "2px",
textDecoration: 'underline',
fontSize: 'inherit',
color: '$textMuted',
textUnderlineOffset: '2px'
},
default: {
backgroundColor: "$mauve12",
boxShadow: "inset 0 0 0 1px $colors$mauve12",
color: "$mauve1",
"@hover": {
"&:hover": {
backgroundColor: "$mauve12",
boxShadow: "inset 0 0 0 1px $colors$mauve12",
backgroundColor: '$mauve12',
boxShadow: 'inset 0 0 0 1px $colors$mauve12',
color: '$mauve1',
'@hover': {
'&:hover': {
backgroundColor: '$mauve12',
boxShadow: 'inset 0 0 0 1px $colors$mauve12'
}
},
'&:active': {
backgroundColor: '$mauve10',
boxShadow: 'inset 0 0 0 1px $colors$mauve11'
},
"&:active": {
backgroundColor: "$mauve10",
boxShadow: "inset 0 0 0 1px $colors$mauve11",
},
"&:focus": {
boxShadow:
"inset 0 0 0 1px $colors$mauve12, inset 0 0 0 2px $colors$mauve12",
'&:focus': {
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"]':
{
backgroundColor: "$mauve4",
boxShadow: "inset 0 0 0 1px $colors$mauve8",
},
backgroundColor: '$mauve4',
boxShadow: 'inset 0 0 0 1px $colors$mauve8'
}
},
primary: {
backgroundColor: `$accent`,
boxShadow: "inset 0 0 0 1px $colors$purple9",
color: "$white",
"@hover": {
"&:hover": {
backgroundColor: "$purple10",
boxShadow: "inset 0 0 0 1px $colors$purple11",
boxShadow: 'inset 0 0 0 1px $colors$purple9',
color: '$white',
'@hover': {
'&:hover': {
backgroundColor: '$purple10',
boxShadow: 'inset 0 0 0 1px $colors$purple11'
}
},
'&:active': {
backgroundColor: '$purple8',
boxShadow: 'inset 0 0 0 1px $colors$purple8'
},
"&:active": {
backgroundColor: "$purple8",
boxShadow: "inset 0 0 0 1px $colors$purple8",
},
"&:focus": {
boxShadow: "inset 0 0 0 2px $colors$purple12",
'&:focus': {
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$purple8",
},
backgroundColor: '$mauve4',
boxShadow: 'inset 0 0 0 1px $colors$purple8'
}
},
secondary: {
backgroundColor: `$purple9`,
boxShadow: "inset 0 0 0 1px $colors$purple9",
color: "$white",
"@hover": {
"&:hover": {
backgroundColor: "$purple10",
boxShadow: "inset 0 0 0 1px $colors$purple11",
boxShadow: 'inset 0 0 0 1px $colors$purple9',
color: '$white',
'@hover': {
'&:hover': {
backgroundColor: '$purple10',
boxShadow: 'inset 0 0 0 1px $colors$purple11'
}
},
'&:active': {
backgroundColor: '$purple8',
boxShadow: 'inset 0 0 0 1px $colors$purple8'
},
"&:active": {
backgroundColor: "$purple8",
boxShadow: "inset 0 0 0 1px $colors$purple8",
},
"&:focus": {
boxShadow: "inset 0 0 0 2px $colors$purple12",
'&:focus': {
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$purple8",
},
backgroundColor: '$mauve4',
boxShadow: 'inset 0 0 0 1px $colors$purple8'
}
},
destroy: {
backgroundColor: `$red9`,
boxShadow: "inset 0 0 0 1px $colors$red9",
color: "$white",
"@hover": {
"&:hover": {
backgroundColor: "$red10",
boxShadow: "inset 0 0 0 1px $colors$red11",
boxShadow: 'inset 0 0 0 1px $colors$red9',
color: '$white',
'@hover': {
'&:hover': {
backgroundColor: '$red10',
boxShadow: 'inset 0 0 0 1px $colors$red11'
}
},
'&:active': {
backgroundColor: '$red8',
boxShadow: 'inset 0 0 0 1px $colors$red8'
},
"&:active": {
backgroundColor: "$red8",
boxShadow: "inset 0 0 0 1px $colors$red8",
},
"&:focus": {
boxShadow: "inset 0 0 0 2px $colors$red12",
'&:focus': {
boxShadow: 'inset 0 0 0 2px $colors$red12'
},
'&[data-radix-popover-trigger][data-state="open"], &[data-radix-dropdown-menu-trigger][data-state="open"]':
{
backgroundColor: "$mauve4",
boxShadow: "inset 0 0 0 1px $colors$red8",
},
},
backgroundColor: '$mauve4',
boxShadow: 'inset 0 0 0 1px $colors$red8'
}
}
},
muted: {
true: {
color: "$textMuted",
},
color: '$textMuted'
}
},
isDisabled: {
true: {
opacity: 0.6,
// pointerEvents: "none",
cursor: "auto",
"&:hover": {
boxShadow: "inherit",
},
},
cursor: 'auto',
'&:hover': {
boxShadow: 'inherit'
}
}
},
outline: {
true: {
backgroundColor: "transparent",
},
backgroundColor: 'transparent'
}
},
uppercase: {
true: {
textTransform: "uppercase",
},
textTransform: 'uppercase'
}
},
fullWidth: {
true: {
width: "100%",
},
width: '100%'
}
},
ghost: {
true: {
boxShadow: "none",
background: "transparent",
color: "$mauve12",
"@hover": {
"&:hover": {
backgroundColor: "$mauve6",
boxShadow: "none",
},
},
"&:active": {
backgroundColor: "$mauve8",
boxShadow: "none",
},
"&:focus": {
boxShadow: "none",
boxShadow: 'none',
background: 'transparent',
color: '$mauve12',
'@hover': {
'&:hover': {
backgroundColor: '$mauve6',
boxShadow: 'none'
}
},
'&:active': {
backgroundColor: '$mauve8',
boxShadow: 'none'
},
'&:focus': {
boxShadow: 'none'
}
}
},
isLoading: {
true: {
"& .button-content": {
visibility: "hidden",
},
pointerEvents: "none",
},
'& .button-content': {
visibility: 'hidden'
},
pointerEvents: 'none'
}
}
},
compoundVariants: [
{
outline: true,
variant: "default",
variant: 'default',
css: {
background: "transparent",
color: "$mauve12",
boxShadow: "inset 0 0 0 1px $colors$mauve10",
"&:hover": {
color: "$mauve12",
background: "$mauve5",
},
},
background: 'transparent',
color: '$mauve12',
boxShadow: 'inset 0 0 0 1px $colors$mauve10',
'&:hover': {
color: '$mauve12',
background: '$mauve5'
}
}
},
{
outline: true,
variant: "primary",
variant: 'primary',
css: {
background: "transparent",
color: "$mauve12",
"&:hover": {
color: "$mauve12",
background: "$mauve5",
},
},
background: 'transparent',
color: '$mauve12',
'&:hover': {
color: '$mauve12',
background: '$mauve5'
}
}
},
{
outline: true,
variant: "secondary",
variant: 'secondary',
css: {
background: "transparent",
color: "$mauve12",
"&:hover": {
color: "$mauve12",
background: "$mauve5",
},
},
},
background: 'transparent',
color: '$mauve12',
'&:hover': {
color: '$mauve12',
background: '$mauve5'
}
}
}
],
defaultVariants: {
size: "md",
variant: "default",
},
});
size: 'md',
variant: 'default'
}
})
const CustomButton: React.FC<
React.ComponentProps<typeof StyledButton> & { as?: string }
> = React.forwardRef(({ children, as = "button", ...rest }, ref) => (
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"
>
<Flex as="span" css={{ gap: '$2', alignItems: 'center' }} className="button-content">
{children}
</Flex>
{rest.isLoading && <Spinner css={{ position: "absolute" }} />}
{rest.isLoading && <Spinner css={{ position: 'absolute' }} />}
</StyledButton>
));
))
CustomButton.displayName = "CustomButton";
CustomButton.displayName = 'CustomButton'
export default CustomButton;
export default CustomButton

View File

@@ -1,29 +1,29 @@
import { styled } from "../stitches.config";
import { StyledButton } from "./Button";
import { styled } from '../stitches.config'
import { StyledButton } from './Button'
const ButtonGroup = styled("div", {
display: "flex",
marginLeft: "1px",
const ButtonGroup = styled('div', {
display: 'flex',
marginLeft: '1px',
[`& ${StyledButton}`]: {
marginLeft: "-1px",
px: "$4",
marginLeft: '-1px',
px: '$4',
zIndex: 2,
position: "relative",
"&:hover, &:focus": {
zIndex: 200,
},
position: 'relative',
'&:hover, &:focus': {
zIndex: 200
}
},
[`& ${StyledButton}:not(:only-of-type):not(:first-child):not(:last-child)`]: {
borderRadius: 0,
borderRadius: 0
},
[`& ${StyledButton}:first-child:not(:only-of-type)`]: {
borderBottomRightRadius: 0,
borderTopRightRadius: 0,
borderTopRightRadius: 0
},
[`& ${StyledButton}:last-child:not(:only-of-type)`]: {
borderBottomLeftRadius: 0,
borderTopLeftRadius: 0,
},
});
borderTopLeftRadius: 0
}
})
export default ButtonGroup;
export default ButtonGroup

View File

@@ -1,12 +1,12 @@
import { styled } from "../stitches.config";
import Box from "./Box";
import { styled } from '../stitches.config'
import Box from './Box'
const Container = styled(Box, {
width: "100%",
marginLeft: "auto",
marginRight: "auto",
px: "$4",
maxWidth: "100%",
});
width: '100%',
marginLeft: 'auto',
marginRight: 'auto',
px: '$4',
maxWidth: '100%'
})
export default Container;
export default Container

View File

@@ -1,6 +1,6 @@
import { CaretRight, Check, Circle } from "phosphor-react";
import { FC, Fragment, ReactNode } from "react";
import { Flex, Text } from "../";
import { CaretRight, Check, Circle } from 'phosphor-react'
import { FC, Fragment, ReactNode } from 'react'
import { Flex, Text } from '../'
import {
ContextMenuCheckboxItem,
ContextMenuContent,
@@ -12,49 +12,40 @@ import {
ContextMenuRoot,
ContextMenuSeparator,
ContextMenuTrigger,
ContextMenuTriggerItem,
} from "./primitive";
ContextMenuTriggerItem
} from './primitive'
export type TextOption = {
type: "text";
label: ReactNode;
onSelect?: () => any;
children?: ContentMenuOption[];
};
export type SeparatorOption = { type: "separator" };
type: 'text'
label: ReactNode
onSelect?: () => any
children?: ContentMenuOption[]
}
export type SeparatorOption = { type: 'separator' }
export type CheckboxOption = {
type: "checkbox";
label: ReactNode;
checked?: boolean;
onCheckedChange?: (isChecked: boolean) => any;
};
type: 'checkbox'
label: ReactNode
checked?: boolean
onCheckedChange?: (isChecked: boolean) => any
}
export type RadioOption<T extends string = string> = {
type: "radio";
label: ReactNode;
onValueChange?: (value: string) => any;
value: T;
options?: { value: T; label?: ReactNode }[];
};
type: 'radio'
label: ReactNode
onValueChange?: (value: string) => any
value: T
options?: { value: T; label?: ReactNode }[]
}
type WithCommons = { key: string; disabled?: boolean };
type WithCommons = { key: string; disabled?: boolean }
export type ContentMenuOption = (
| TextOption
| SeparatorOption
| CheckboxOption
| RadioOption
) &
WithCommons;
export type ContentMenuOption = (TextOption | SeparatorOption | CheckboxOption | RadioOption) &
WithCommons
export interface IContextMenu {
options?: ContentMenuOption[];
isNested?: boolean;
options?: ContentMenuOption[]
isNested?: boolean
}
export const ContextMenu: FC<IContextMenu> = ({
children,
options,
isNested,
}) => {
export const ContextMenu: FC<IContextMenu> = ({ children, options, isNested }) => {
return (
<ContextMenuRoot>
{isNested ? (
@@ -65,8 +56,8 @@ export const ContextMenu: FC<IContextMenu> = ({
{options && !!options.length && (
<ContextMenuContent sideOffset={isNested ? 2 : 5}>
{options.map(({ key, ...option }) => {
if (option.type === "text") {
const { children, label, onSelect } = option;
if (option.type === 'text') {
const { children, label, onSelect } = option
if (children)
return (
<ContextMenu isNested key={key} options={children}>
@@ -75,15 +66,15 @@ export const ContextMenu: FC<IContextMenu> = ({
<CaretRight />
</Flex>
</ContextMenu>
);
)
return (
<ContextMenuItem key={key} onSelect={onSelect}>
{label}
</ContextMenuItem>
);
)
}
if (option.type === "checkbox") {
const { label, checked, onCheckedChange } = option;
if (option.type === 'checkbox') {
const { label, checked, onCheckedChange } = option
return (
<ContextMenuCheckboxItem
key={key}
@@ -94,42 +85,37 @@ export const ContextMenu: FC<IContextMenu> = ({
<ContextMenuItemIndicator>
<Check />
</ContextMenuItemIndicator>
<Text css={{ ml: checked ? "$4" : undefined }}>
{label}
</Text>
<Text css={{ ml: checked ? '$4' : undefined }}>{label}</Text>
</Flex>
</ContextMenuCheckboxItem>
);
)
}
if (option.type === "radio") {
const { label, options, onValueChange, value } = option;
if (option.type === 'radio') {
const { label, options, onValueChange, value } = option
return (
<Fragment key={key}>
<ContextMenuLabel>{label}</ContextMenuLabel>
<ContextMenuRadioGroup
value={value}
onValueChange={onValueChange}
>
<ContextMenuRadioGroup value={value} onValueChange={onValueChange}>
{options?.map(({ value: v, label }) => {
return (
<ContextMenuRadioItem key={v} value={v}>
<ContextMenuItemIndicator>
<Circle weight="fill" />
</ContextMenuItemIndicator>
<Text css={{ ml: "$4" }}>{label}</Text>
<Text css={{ ml: '$4' }}>{label}</Text>
</ContextMenuRadioItem>
);
)
})}
</ContextMenuRadioGroup>
</Fragment>
);
)
}
return <ContextMenuSeparator key={key} />;
return <ContextMenuSeparator key={key} />
})}
</ContextMenuContent>
)}
</ContextMenuRoot>
);
};
)
}
export default ContextMenu;
export default ContextMenu

View File

@@ -1,107 +1,107 @@
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
import { styled } from "../../stitches.config";
import * as ContextMenuPrimitive from '@radix-ui/react-context-menu'
import { styled } from '../../stitches.config'
import {
slideDownAndFade,
slideLeftAndFade,
slideRightAndFade,
slideUpAndFade,
} from "../../styles/keyframes";
slideUpAndFade
} from '../../styles/keyframes'
const StyledContent = styled(ContextMenuPrimitive.Content, {
minWidth: 140,
backgroundColor: "$backgroundOverlay",
backgroundColor: '$backgroundOverlay',
borderRadius: 6,
overflow: "hidden",
padding: "5px",
overflow: 'hidden',
padding: '5px',
boxShadow:
"0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2)",
"@media (prefers-reduced-motion: no-preference)": {
animationDuration: "400ms",
animationTimingFunction: "cubic-bezier(0.16, 1, 0.3, 1)",
willChange: "transform, opacity",
'0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2)',
'@media (prefers-reduced-motion: no-preference)': {
animationDuration: '400ms',
animationTimingFunction: 'cubic-bezier(0.16, 1, 0.3, 1)',
willChange: 'transform, opacity',
'&[data-state="open"]': {
'&[data-side="top"]': { animationName: slideDownAndFade },
'&[data-side="right"]': { animationName: slideLeftAndFade },
'&[data-side="bottom"]': { animationName: slideUpAndFade },
'&[data-side="left"]': { animationName: slideRightAndFade },
'&[data-side="left"]': { animationName: slideRightAndFade }
}
},
},
".dark &": {
'.dark &': {
boxShadow:
"0px 10px 38px -10px rgba(22, 23, 24, 0.85), 0px 10px 20px -15px rgba(22, 23, 24, 0.6)",
},
});
'0px 10px 38px -10px rgba(22, 23, 24, 0.85), 0px 10px 20px -15px rgba(22, 23, 24, 0.6)'
}
})
const itemStyles = {
all: "unset",
all: 'unset',
fontSize: 13,
lineHeight: 1,
color: "$text",
color: '$text',
borderRadius: 3,
display: "flex",
alignItems: "center",
display: 'flex',
alignItems: 'center',
height: 28,
padding: "0 7px",
position: "relative",
padding: '0 7px',
position: 'relative',
paddingLeft: 10,
userSelect: "none",
userSelect: 'none',
"&[data-disabled]": {
color: "$textMuted",
pointerEvents: "none",
'&[data-disabled]': {
color: '$textMuted',
pointerEvents: 'none'
},
"&:focus": {
backgroundColor: "$purple9",
color: "$white",
},
};
'&:focus': {
backgroundColor: '$purple9',
color: '$white'
}
}
const StyledItem = styled(ContextMenuPrimitive.Item, { ...itemStyles });
const StyledItem = styled(ContextMenuPrimitive.Item, { ...itemStyles })
const StyledCheckboxItem = styled(ContextMenuPrimitive.CheckboxItem, {
...itemStyles,
});
...itemStyles
})
const StyledRadioItem = styled(ContextMenuPrimitive.RadioItem, {
...itemStyles,
});
...itemStyles
})
const StyledTriggerItem = styled(ContextMenuPrimitive.TriggerItem, {
'&[data-state="open"]': {
backgroundColor: "$purple9",
color: "$purple9",
backgroundColor: '$purple9',
color: '$purple9'
},
...itemStyles,
});
...itemStyles
})
const StyledLabel = styled(ContextMenuPrimitive.Label, {
paddingLeft: 10,
fontSize: 12,
lineHeight: "25px",
color: "$text",
});
lineHeight: '25px',
color: '$text'
})
const StyledSeparator = styled(ContextMenuPrimitive.Separator, {
height: 1,
backgroundColor: "$backgroundAlt",
margin: 5,
});
backgroundColor: '$backgroundAlt',
margin: 5
})
const StyledItemIndicator = styled(ContextMenuPrimitive.ItemIndicator, {
position: "absolute",
position: 'absolute',
left: 0,
width: 25,
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
});
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center'
})
export const ContextMenuRoot = ContextMenuPrimitive.Root;
export const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
export const ContextMenuContent = StyledContent;
export const ContextMenuItem = StyledItem;
export const ContextMenuCheckboxItem = StyledCheckboxItem;
export const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
export const ContextMenuRadioItem = StyledRadioItem;
export const ContextMenuItemIndicator = StyledItemIndicator;
export const ContextMenuTriggerItem = StyledTriggerItem;
export const ContextMenuLabel = StyledLabel;
export const ContextMenuSeparator = StyledSeparator;
export const ContextMenuRoot = ContextMenuPrimitive.Root
export const ContextMenuTrigger = ContextMenuPrimitive.Trigger
export const ContextMenuContent = StyledContent
export const ContextMenuItem = StyledItem
export const ContextMenuCheckboxItem = StyledCheckboxItem
export const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
export const ContextMenuRadioItem = StyledRadioItem
export const ContextMenuItemIndicator = StyledItemIndicator
export const ContextMenuTriggerItem = StyledTriggerItem
export const ContextMenuLabel = StyledLabel
export const ContextMenuSeparator = StyledSeparator

View File

@@ -1,113 +1,113 @@
import { useEffect } from "react";
import ReconnectingWebSocket, { CloseEvent } from "reconnecting-websocket";
import { proxy, ref, useSnapshot } from "valtio";
import { subscribeKey } from "valtio/utils";
import { Select } from ".";
import state, { ILog, transactionsState } from "../state";
import { extractJSON } from "../utils/json";
import LogBox from "./LogBox";
import { useEffect } from 'react'
import ReconnectingWebSocket, { CloseEvent } from 'reconnecting-websocket'
import { proxy, ref, useSnapshot } from 'valtio'
import { subscribeKey } from 'valtio/utils'
import { Select } from '.'
import state, { ILog, transactionsState } from '../state'
import { extractJSON } from '../utils/json'
import LogBox from './LogBox'
interface ISelect<T = string> {
label: string;
value: T;
label: string
value: T
}
export interface IStreamState {
selectedAccount: ISelect | null;
status: "idle" | "opened" | "closed";
statusChangeTimestamp?: number;
logs: ILog[];
socket?: ReconnectingWebSocket;
selectedAccount: ISelect | null
status: 'idle' | 'opened' | 'closed'
statusChangeTimestamp?: number
logs: ILog[]
socket?: ReconnectingWebSocket
}
export const streamState = proxy<IStreamState>({
selectedAccount: null as ISelect | null,
status: "idle",
logs: [] as ILog[],
});
status: 'idle',
logs: [] as ILog[]
})
const onOpen = (account: ISelect | null) => {
if (!account) {
return;
return
}
// streamState.logs = [];
streamState.status = "opened";
streamState.statusChangeTimestamp = Date.now();
streamState.status = 'opened'
streamState.statusChangeTimestamp = Date.now()
pushLog(`Debug stream opened for account ${account?.value}`, {
type: "success",
});
};
type: 'success'
})
}
const onError = () => {
pushLog("Something went wrong! Check your connection and try again.", {
type: "error",
});
};
pushLog('Something went wrong! Check your connection and try again.', {
type: 'error'
})
}
const onClose = (e: CloseEvent) => {
// 999 = closed websocket connection by switching account
if (e.code !== 4999) {
pushLog(`Connection was closed. [code: ${e.code}]`, {
type: "error",
});
type: 'error'
})
}
streamState.status = "closed";
streamState.statusChangeTimestamp = Date.now();
};
streamState.status = 'closed'
streamState.statusChangeTimestamp = Date.now()
}
const onMessage = (event: any) => {
// Ping returns just account address, if we get that
// response we don't need to log anything
if (event.data !== streamState.selectedAccount?.value) {
pushLog(event.data);
pushLog(event.data)
}
};
}
let interval: NodeJS.Timer | null = null;
let interval: NodeJS.Timer | null = null
const addListeners = (account: ISelect | null) => {
if (account?.value && streamState.socket?.url.endsWith(account?.value)) {
return;
return
}
streamState.logs = [];
streamState.logs = []
if (account?.value) {
if (interval) {
clearInterval(interval);
clearInterval(interval)
}
if (streamState.socket) {
streamState.socket?.removeEventListener("open", () => onOpen(account));
streamState.socket?.removeEventListener("close", onClose);
streamState.socket?.removeEventListener("error", onError);
streamState.socket?.removeEventListener("message", onMessage);
streamState.socket?.removeEventListener('open', () => onOpen(account))
streamState.socket?.removeEventListener('close', onClose)
streamState.socket?.removeEventListener('error', onError)
streamState.socket?.removeEventListener('message', onMessage)
}
streamState.socket = ref(
new ReconnectingWebSocket(
`wss://${process.env.NEXT_PUBLIC_DEBUG_STREAM_URL}/${account?.value}`
)
);
)
if (streamState.socket) {
interval = setInterval(() => {
streamState.socket?.send("");
}, 45000);
streamState.socket?.send('')
}, 45000)
}
streamState.socket.addEventListener("open", () => onOpen(account));
streamState.socket.addEventListener("close", onClose);
streamState.socket.addEventListener("error", onError);
streamState.socket.addEventListener("message", onMessage);
streamState.socket.addEventListener('open', () => onOpen(account))
streamState.socket.addEventListener('close', onClose)
streamState.socket.addEventListener('error', onError)
streamState.socket.addEventListener('message', onMessage)
}
};
}
subscribeKey(streamState, "selectedAccount", addListeners);
subscribeKey(streamState, 'selectedAccount', addListeners)
const DebugStream = () => {
const { selectedAccount, logs } = useSnapshot(streamState);
const { activeHeader: activeTxTab } = useSnapshot(transactionsState);
const { accounts } = useSnapshot(state);
const { selectedAccount, logs } = useSnapshot(streamState)
const { activeHeader: activeTxTab } = useSnapshot(transactionsState)
const { accounts } = useSnapshot(state)
const accountOptions = accounts.map((acc) => ({
const accountOptions = accounts.map(acc => ({
label: acc.name,
value: acc.address,
}));
value: acc.address
}))
const renderNav = () => (
<>
@@ -117,80 +117,63 @@ const DebugStream = () => {
options={accountOptions}
hideSelectedOptions
value={selectedAccount}
onChange={(acc) => {
streamState.socket?.close(
4999,
"Old connection closed because user switched account"
);
streamState.selectedAccount = acc as any;
onChange={acc => {
streamState.socket?.close(4999, 'Old connection closed because user switched account')
streamState.selectedAccount = acc as any
}}
css={{ width: "100%" }}
css={{ width: '100%' }}
/>
</>
);
)
useEffect(() => {
const account = transactionsState.transactions.find(
(tx) => tx.header === activeTxTab
)?.state.selectedAccount;
const account = transactionsState.transactions.find(tx => tx.header === activeTxTab)?.state
.selectedAccount
if (account && account.value !== streamState.selectedAccount?.value)
streamState.selectedAccount = account;
}, [activeTxTab]);
streamState.selectedAccount = account
}, [activeTxTab])
const clearLog = () => {
streamState.logs = [];
streamState.statusChangeTimestamp = Date.now();
};
return (
<LogBox
enhanced
renderNav={renderNav}
title="Debug stream"
logs={logs}
clearLog={clearLog}
/>
);
};
export default DebugStream;
export const pushLog = (
str: any,
opts: Partial<Pick<ILog, "type">> = {}
): ILog | undefined => {
if (!str) return;
if (typeof str !== "string") throw Error("Unrecognized debug log stream!");
const match = str.match(/([\s\S]+(?:UTC|ISO|GMT[+|-]\d+))?\ ?([\s\S]*)/m);
const [_, tm, msg] = match || [];
const timestamp = Date.parse(tm || "") || undefined;
const timestring = !timestamp ? tm : new Date(timestamp).toLocaleTimeString();
const extracted = extractJSON(msg);
const message = !extracted
? msg
: msg.slice(0, extracted.start) + msg.slice(extracted.end + 1);
const jsonData = extracted
? JSON.stringify(extracted.result, null, 2)
: undefined;
if (extracted?.result?.id?._Request?.includes("hooks-builder-req")) {
return;
streamState.logs = []
streamState.statusChangeTimestamp = Date.now()
}
const { type = "log" } = opts;
return (
<LogBox enhanced renderNav={renderNav} title="Debug stream" logs={logs} clearLog={clearLog} />
)
}
export default DebugStream
export const pushLog = (str: any, opts: Partial<Pick<ILog, 'type'>> = {}): ILog | undefined => {
if (!str) return
if (typeof str !== 'string') throw Error('Unrecognized debug log stream!')
const match = str.match(/([\s\S]+(?:UTC|ISO|GMT[+|-]\d+))?\ ?([\s\S]*)/m)
const [_, tm, msg] = match || []
const timestamp = Date.parse(tm || '') || undefined
const timestring = !timestamp ? tm : new Date(timestamp).toLocaleTimeString()
const extracted = extractJSON(msg)
const message = !extracted ? msg : msg.slice(0, extracted.start) + msg.slice(extracted.end + 1)
const jsonData = extracted ? JSON.stringify(extracted.result, null, 2) : undefined
if (extracted?.result?.id?._Request?.includes('hooks-builder-req')) {
return
}
const { type = 'log' } = opts
const log: ILog = {
type,
message,
timestring,
jsonData,
defaultCollapsed: true,
};
defaultCollapsed: true
}
if (log) streamState.logs.push(log);
return log;
};
if (log) streamState.logs.push(log)
return log
}

View File

@@ -1,54 +1,50 @@
import React, { useState } from "react";
import { useSnapshot } from "valtio";
import React, { useState } from 'react'
import { useSnapshot } from 'valtio'
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 { useTheme } from 'next-themes'
import { useRouter } from 'next/router'
import NextLink from 'next/link'
import ReactTimeAgo from 'react-time-ago'
import filesize from 'filesize'
import Box from "./Box";
import Container from "./Container";
import state from "../state";
import wat from "../utils/wat-highlight";
import Box from './Box'
import Container from './Container'
import state from '../state'
import wat from '../utils/wat-highlight'
import EditorNavigation from "./EditorNavigation";
import { Button, Text, Link, Flex, Tabs, Tab } from ".";
import Monaco from "./Monaco";
import EditorNavigation from './EditorNavigation'
import { Button, Text, Link, Flex, Tabs, Tab } from '.'
import Monaco from './Monaco'
const FILESIZE_BREAKPOINTS: [number, number] = [2 * 1024, 5 * 1024];
const FILESIZE_BREAKPOINTS: [number, number] = [2 * 1024, 5 * 1024]
const DeployEditor = () => {
const snap = useSnapshot(state);
const router = useRouter();
const { theme } = useTheme();
const snap = useSnapshot(state)
const router = useRouter()
const { theme } = useTheme()
const [showContent, setShowContent] = useState(false);
const [showContent, setShowContent] = useState(false)
const compiledFiles = snap.files.filter(file => file.compiledContent);
const activeFile = compiledFiles[snap.activeWat];
const compiledFiles = snap.files.filter(file => file.compiledContent)
const activeFile = compiledFiles[snap.activeWat]
const renderNav = () => (
<Tabs
activeIndex={snap.activeWat}
onChangeActive={idx => (state.activeWat = idx)}
>
<Tabs activeIndex={snap.activeWat} onChangeActive={idx => (state.activeWat = idx)}>
{compiledFiles.map((file, index) => {
return <Tab key={file.name} header={`${file.name}.wat`} />;
return <Tab key={file.name} header={`${file.name}.wat`} />
})}
</Tabs>
);
)
const compiledSize = activeFile?.compiledContent?.byteLength || 0;
const compiledSize = activeFile?.compiledContent?.byteLength || 0
const color =
compiledSize > FILESIZE_BREAKPOINTS[1]
? "$error"
? '$error'
: compiledSize > FILESIZE_BREAKPOINTS[0]
? "$warning"
: "$success";
? '$warning'
: '$success'
const isContentChanged =
activeFile && activeFile.compiledValueSnapshot !== activeFile.content;
const isContentChanged = activeFile && activeFile.compiledValueSnapshot !== activeFile.content
// const hasDeployErrors = activeFile && activeFile.containsErrors;
const CompiledStatView = activeFile && (
@@ -56,29 +52,22 @@ const DeployEditor = () => {
column
align="center"
css={{
fontSize: "$sm",
fontFamily: "$monospace",
textAlign: "center",
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" />
)}
<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>
<Text css={{ ml: '$2', color }}>({filesize(activeFile.compiledContent.byteLength)})</Text>
)}
</Flex>
{activeFile.compiledContent?.byteLength &&
activeFile.compiledContent?.byteLength >= 64000 && (
<Flex css={{ flexDirection: "column", py: "$3", pb: "$1" }}>
<Text css={{ ml: "$2", color: "$error" }}>
{activeFile.compiledContent?.byteLength && activeFile.compiledContent?.byteLength >= 64000 && (
<Flex css={{ flexDirection: 'column', py: '$3', pb: '$1' }}>
<Text css={{ ml: '$2', color: '$error' }}>
File size is larger than 64kB, cannot set hook!
</Text>
</Flex>
@@ -88,20 +77,20 @@ const DeployEditor = () => {
</Button>
{isContentChanged && (
<Text warning>
File contents were changed after last compile, compile again to
incorporate your latest changes in the build.
File contents were changed after last compile, compile again to incorporate your latest
changes in the build.
</Text>
)}
</Flex>
);
)
const NoContentView = !snap.loading && router.isReady && (
<Text
css={{
mt: "-60px",
fontSize: "$sm",
fontFamily: "$monospace",
maxWidth: "300px",
textAlign: "center",
mt: '-60px',
fontSize: '$sm',
fontFamily: '$monospace',
maxWidth: '300px',
textAlign: 'center'
}}
>
{`You haven't compiled any files yet, compile files on `}
@@ -109,29 +98,27 @@ const DeployEditor = () => {
<Link as="a">develop view</Link>
</NextLink>
</Text>
);
)
const isContent =
snap.files?.filter(file => file.compiledWatContent).length > 0 &&
router.isReady;
const isContent = snap.files?.filter(file => file.compiledWatContent).length > 0 && router.isReady
return (
<Box
css={{
flex: 1,
display: "flex",
position: "relative",
flexDirection: "column",
backgroundColor: "$mauve2",
width: "100%",
display: 'flex',
position: 'relative',
flexDirection: 'column',
backgroundColor: '$mauve2',
width: '100%'
}}
>
<EditorNavigation renderNav={renderNav} />
<Container
css={{
display: "flex",
display: 'flex',
flex: 1,
justifyContent: "center",
alignItems: "center",
justifyContent: 'center',
alignItems: 'center'
}}
>
{!isContent ? (
@@ -141,41 +128,39 @@ const DeployEditor = () => {
) : (
<Monaco
className="hooks-editor"
defaultLanguage={"wat"}
language={"wat"}
defaultLanguage={'wat'}
language={'wat'}
path={`file://tmp/c/${activeFile?.name}.wat`}
value={activeFile?.compiledWatContent || ""}
value={activeFile?.compiledWatContent || ''}
beforeMount={monaco => {
monaco.languages.register({ id: "wat" });
monaco.languages.setLanguageConfiguration("wat", wat.config);
monaco.languages.setMonarchTokensProvider("wat", wat.tokens);
monaco.languages.register({ id: 'wat' })
monaco.languages.setLanguageConfiguration('wat', wat.config)
monaco.languages.setMonarchTokensProvider('wat', wat.tokens)
}}
onMount={editor => {
editor.updateOptions({
glyphMargin: true,
readOnly: true,
});
readOnly: true
})
}}
theme={theme === "dark" ? "dark" : "light"}
theme={theme === 'dark' ? 'dark' : 'light'}
overlay={
<Flex
css={{
m: "$1",
ml: "auto",
fontSize: "$sm",
color: "$textMuted",
m: '$1',
ml: 'auto',
fontSize: '$sm',
color: '$textMuted'
}}
>
<Link onClick={() => setShowContent(false)}>
Exit editor mode
</Link>
<Link onClick={() => setShowContent(false)}>Exit editor mode</Link>
</Flex>
}
/>
)}
</Container>
</Box>
);
};
)
}
export default DeployEditor;
export default DeployEditor

View File

@@ -1,90 +1,88 @@
import React from "react";
import * as Stiches from "@stitches/react";
import { keyframes } from "@stitches/react";
import { blackA } from "@radix-ui/colors";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { styled } from "../stitches.config";
import React from 'react'
import * as Stiches from '@stitches/react'
import { keyframes } from '@stitches/react'
import { blackA } from '@radix-ui/colors'
import * as DialogPrimitive from '@radix-ui/react-dialog'
import { styled } from '../stitches.config'
const overlayShow = keyframes({
"0%": { opacity: 0.01 },
"100%": { opacity: 1 },
});
'0%': { opacity: 0.01 },
'100%': { opacity: 1 }
})
const contentShow = keyframes({
"0%": { opacity: 0.01 },
"100%": { opacity: 1 },
});
'0%': { opacity: 0.01 },
'100%': { opacity: 1 }
})
const StyledOverlay = styled(DialogPrimitive.Overlay, {
zIndex: 10000,
backgroundColor: blackA.blackA9,
position: "fixed",
position: 'fixed',
inset: 0,
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "grid",
placeItems: "center",
overflowY: "auto",
"@media (prefers-reduced-motion: no-preference)": {
animation: `${overlayShow} 250ms cubic-bezier(0.16, 1, 0.3, 1)`,
display: 'grid',
placeItems: 'center',
overflowY: 'auto',
'@media (prefers-reduced-motion: no-preference)': {
animation: `${overlayShow} 250ms cubic-bezier(0.16, 1, 0.3, 1)`
},
".dark &": {
backgroundColor: blackA.blackA11,
},
});
'.dark &': {
backgroundColor: blackA.blackA11
}
})
const StyledContent = styled(DialogPrimitive.Content, {
zIndex: 1000,
backgroundColor: "$mauve2",
color: "$mauve12",
borderRadius: "$md",
position: "relative",
mb: "15%",
boxShadow:
"0px 10px 38px -5px rgba(22, 23, 24, 0.25), 0px 10px 20px -5px rgba(22, 23, 24, 0.2)",
width: "90vw",
maxWidth: "450px",
backgroundColor: '$mauve2',
color: '$mauve12',
borderRadius: '$md',
position: 'relative',
mb: '15%',
boxShadow: '0px 10px 38px -5px rgba(22, 23, 24, 0.25), 0px 10px 20px -5px rgba(22, 23, 24, 0.2)',
width: '90vw',
maxWidth: '450px',
// maxHeight: "85vh",
padding: 25,
"@media (prefers-reduced-motion: no-preference)": {
animation: `${contentShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
'@media (prefers-reduced-motion: no-preference)': {
animation: `${contentShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`
},
"&:focus": { outline: "none" },
".dark &": {
backgroundColor: "$mauve5",
boxShadow:
"0px 10px 38px 0px rgba(0, 0, 0, 0.85), 0px 10px 20px 0px rgba(0, 0, 0, 0.6)",
},
});
'&:focus': { outline: 'none' },
'.dark &': {
backgroundColor: '$mauve5',
boxShadow: '0px 10px 38px 0px rgba(0, 0, 0, 0.85), 0px 10px 20px 0px rgba(0, 0, 0, 0.6)'
}
})
const Content: React.FC<{ css?: Stiches.CSS }> = ({ css, children }) => {
return (
<StyledOverlay>
<StyledContent css={css}>{children}</StyledContent>
</StyledOverlay>
);
};
)
}
const StyledTitle = styled(DialogPrimitive.Title, {
margin: 0,
fontWeight: 500,
color: "$mauve12",
fontSize: 17,
});
color: '$mauve12',
fontSize: 17
})
const StyledDescription = styled(DialogPrimitive.Description, {
margin: "10px 0 10px",
color: "$mauve11",
margin: '10px 0 10px',
color: '$mauve11',
fontSize: 15,
lineHeight: 1.5,
});
lineHeight: 1.5
})
// Exports
export const Dialog = styled(DialogPrimitive.Root);
export const DialogTrigger = DialogPrimitive.Trigger;
export const DialogContent = Content;
export const DialogTitle = StyledTitle;
export const DialogDescription = StyledDescription;
export const DialogClose = DialogPrimitive.Close;
export const DialogPortal = DialogPrimitive.Portal;
export const Dialog = styled(DialogPrimitive.Root)
export const DialogTrigger = DialogPrimitive.Trigger
export const DialogContent = Content
export const DialogTitle = StyledTitle
export const DialogDescription = StyledDescription
export const DialogClose = DialogPrimitive.Close
export const DialogPortal = DialogPrimitive.Portal

View File

@@ -1,115 +1,120 @@
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
import { styled } from "../stitches.config";
import { slideDownAndFade, slideLeftAndFade, slideRightAndFade, slideUpAndFade } from '../styles/keyframes';
import { styled } from '../stitches.config'
import {
slideDownAndFade,
slideLeftAndFade,
slideRightAndFade,
slideUpAndFade
} from '../styles/keyframes'
const StyledContent = styled(DropdownMenuPrimitive.Content, {
minWidth: 220,
backgroundColor: "$mauve2",
backgroundColor: '$mauve2',
borderRadius: 6,
padding: 5,
boxShadow:
"0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2)",
"@media (prefers-reduced-motion: no-preference)": {
animationDuration: "400ms",
animationTimingFunction: "cubic-bezier(0.16, 1, 0.3, 1)",
willChange: "transform, opacity",
'0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2)',
'@media (prefers-reduced-motion: no-preference)': {
animationDuration: '400ms',
animationTimingFunction: 'cubic-bezier(0.16, 1, 0.3, 1)',
willChange: 'transform, opacity',
'&[data-state="open"]': {
'&[data-side="top"]': { animationName: slideDownAndFade },
'&[data-side="right"]': { animationName: slideLeftAndFade },
'&[data-side="bottom"]': { animationName: slideUpAndFade },
'&[data-side="left"]': { animationName: slideRightAndFade },
'&[data-side="left"]': { animationName: slideRightAndFade }
}
},
},
".dark &": {
backgroundColor: "$mauve5",
'.dark &': {
backgroundColor: '$mauve5',
boxShadow:
"0px 10px 38px -10px rgba(22, 23, 24, 0.85), 0px 10px 20px -15px rgba(22, 23, 24, 0.6)",
},
});
'0px 10px 38px -10px rgba(22, 23, 24, 0.85), 0px 10px 20px -15px rgba(22, 23, 24, 0.6)'
}
})
const itemStyles = {
all: "unset",
all: 'unset',
fontSize: 13,
lineHeight: 1,
color: "$mauve12",
color: '$mauve12',
borderRadius: 3,
display: "flex",
alignItems: "center",
display: 'flex',
alignItems: 'center',
height: 32,
padding: "0 5px",
position: "relative",
paddingLeft: "5px",
userSelect: "none",
py: "$0.5",
pr: "$2",
gap: "$2",
padding: '0 5px',
position: 'relative',
paddingLeft: '5px',
userSelect: 'none',
py: '$0.5',
pr: '$2',
gap: '$2',
"&[data-disabled]": {
color: "$mauve9",
pointerEvents: "none",
'&[data-disabled]': {
color: '$mauve9',
pointerEvents: 'none'
},
"&:focus": {
backgroundColor: "$purple9",
color: "$white",
},
};
'&:focus': {
backgroundColor: '$purple9',
color: '$white'
}
}
const StyledItem = styled(DropdownMenuPrimitive.Item, { ...itemStyles });
const StyledItem = styled(DropdownMenuPrimitive.Item, { ...itemStyles })
const StyledCheckboxItem = styled(DropdownMenuPrimitive.CheckboxItem, {
...itemStyles,
});
...itemStyles
})
const StyledRadioItem = styled(DropdownMenuPrimitive.RadioItem, {
...itemStyles,
});
...itemStyles
})
const StyledTriggerItem = styled(DropdownMenuPrimitive.TriggerItem, {
'&[data-state="open"]': {
backgroundColor: "$purple9",
color: "$purple9",
backgroundColor: '$purple9',
color: '$purple9'
},
...itemStyles,
});
...itemStyles
})
const StyledLabel = styled(DropdownMenuPrimitive.Label, {
paddingLeft: 25,
fontSize: 12,
lineHeight: "25px",
color: "$mauve11",
});
lineHeight: '25px',
color: '$mauve11'
})
const StyledSeparator = styled(DropdownMenuPrimitive.Separator, {
height: 1,
backgroundColor: "$mauve7",
margin: 5,
});
backgroundColor: '$mauve7',
margin: 5
})
const StyledItemIndicator = styled(DropdownMenuPrimitive.ItemIndicator, {
position: "absolute",
position: 'absolute',
left: 0,
width: 25,
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
});
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center'
})
const StyledArrow = styled(DropdownMenuPrimitive.Arrow, {
fill: "$mauve2",
".dark &": {
fill: "$mauve5",
},
});
fill: '$mauve2',
'.dark &': {
fill: '$mauve5'
}
})
// Exports
export const DropdownMenu = DropdownMenuPrimitive.Root;
export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
export const DropdownMenuContent = StyledContent;
export const DropdownMenuItem = StyledItem;
export const DropdownMenuCheckboxItem = StyledCheckboxItem;
export const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
export const DropdownMenuRadioItem = StyledRadioItem;
export const DropdownMenuItemIndicator = StyledItemIndicator;
export const DropdownMenuTriggerItem = StyledTriggerItem;
export const DropdownMenuLabel = StyledLabel;
export const DropdownMenuSeparator = StyledSeparator;
export const DropdownMenuArrow = StyledArrow;
export const DropdownMenu = DropdownMenuPrimitive.Root
export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
export const DropdownMenuContent = StyledContent
export const DropdownMenuItem = StyledItem
export const DropdownMenuCheckboxItem = StyledCheckboxItem
export const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
export const DropdownMenuRadioItem = StyledRadioItem
export const DropdownMenuItemIndicator = StyledItemIndicator
export const DropdownMenuTriggerItem = StyledTriggerItem
export const DropdownMenuLabel = StyledLabel
export const DropdownMenuSeparator = StyledSeparator
export const DropdownMenuArrow = StyledArrow

View File

@@ -1,9 +1,4 @@
import React, {
useState,
useEffect,
useRef,
ReactNode,
} from "react";
import React, { useState, useEffect, useRef, ReactNode } from 'react'
import {
Share,
DownloadSimple,
@@ -15,120 +10,113 @@ import {
CloudArrowUp,
CaretDown,
User,
FilePlus,
} from "phosphor-react";
import Image from "next/image";
FilePlus
} from 'phosphor-react'
import Image from 'next/image'
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuArrow,
DropdownMenuSeparator,
} from "./DropdownMenu";
import NewWindow from "react-new-window";
import { signOut, useSession } from "next-auth/react";
import { useSnapshot } from "valtio";
import toast from "react-hot-toast";
DropdownMenuSeparator
} from './DropdownMenu'
import NewWindow from 'react-new-window'
import { signOut, useSession } from 'next-auth/react'
import { useSnapshot } from 'valtio'
import toast from 'react-hot-toast'
import {
syncToGist,
updateEditorSettings,
downloadAsZip,
} from "../state/actions";
import state from "../state";
import Box from "./Box";
import Button from "./Button";
import Container from "./Container";
import { syncToGist, updateEditorSettings, downloadAsZip } from '../state/actions'
import state from '../state'
import Box from './Box'
import Button from './Button'
import Container from './Container'
import {
Dialog,
DialogTrigger,
DialogContent,
DialogTitle,
DialogDescription,
DialogClose,
} from "./Dialog";
import Flex from "./Flex";
import Stack from "./Stack";
import { Input, Label } from "./Input";
import Tooltip from "./Tooltip";
import { showAlert } from "../state/actions/showAlert";
DialogClose
} from './Dialog'
import Flex from './Flex'
import Stack from './Stack'
import { Input, Label } from './Input'
import Tooltip from './Tooltip'
import { showAlert } from '../state/actions/showAlert'
const EditorNavigation = ({ renderNav }: { renderNav?: () => ReactNode }) => {
const snap = useSnapshot(state);
const [editorSettingsOpen, setEditorSettingsOpen] = useState(false);
const { data: session, status } = useSession();
const [popup, setPopUp] = useState(false);
const [editorSettings, setEditorSettings] = useState(snap.editorSettings);
const snap = useSnapshot(state)
const [editorSettingsOpen, setEditorSettingsOpen] = useState(false)
const { data: session, status } = useSession()
const [popup, setPopUp] = useState(false)
const [editorSettings, setEditorSettings] = useState(snap.editorSettings)
useEffect(() => {
if (session && session.user && popup) {
setPopUp(false);
setPopUp(false)
}
}, [session, popup]);
}, [session, popup])
const showNewGistAlert = () => {
showAlert("Are you sure?", {
showAlert('Are you sure?', {
body: (
<>
This action will create new <strong>public</strong> Github Gist from
your current saved files. You can delete gist anytime from your GitHub
Gists page.
This action will create new <strong>public</strong> Github Gist from your current saved
files. You can delete gist anytime from your GitHub Gists page.
</>
),
cancelText: "Cancel",
confirmText: "Create new Gist",
cancelText: 'Cancel',
confirmText: 'Create new Gist',
confirmPrefix: <FilePlus size="15px" />,
onConfirm: () => syncToGist(session, true),
});
};
onConfirm: () => syncToGist(session, true)
})
}
const scrollRef = useRef<HTMLDivElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const scrollRef = useRef<HTMLDivElement>(null)
const containerRef = useRef<HTMLDivElement>(null)
return (
<Flex css={{ flexShrink: 0, gap: "$0" }}>
<Flex css={{ flexShrink: 0, gap: '$0' }}>
<Flex
id="kissa"
ref={scrollRef}
css={{
overflowX: "scroll",
overflowY: "hidden",
py: "$3",
pb: "$0",
overflowX: 'scroll',
overflowY: 'hidden',
py: '$3',
pb: '$0',
flex: 1,
"&::-webkit-scrollbar": {
height: "0.3em",
background: "rgba(0,0,0,.0)",
'&::-webkit-scrollbar': {
height: '0.3em',
background: 'rgba(0,0,0,.0)'
},
"&::-webkit-scrollbar-gutter": "stable",
"&::-webkit-scrollbar-thumb": {
backgroundColor: "rgba(0,0,0,.2)",
outline: "0px",
borderRadius: "9999px",
'&::-webkit-scrollbar-gutter': 'stable',
'&::-webkit-scrollbar-thumb': {
backgroundColor: 'rgba(0,0,0,.2)',
outline: '0px',
borderRadius: '9999px'
},
scrollbarColor: "rgba(0,0,0,.2) rgba(0,0,0,0)",
scrollbarGutter: "stable",
scrollbarWidth: "thin",
".dark &": {
"&::-webkit-scrollbar": {
background: "rgba(0,0,0,.0)",
scrollbarColor: 'rgba(0,0,0,.2) rgba(0,0,0,0)',
scrollbarGutter: 'stable',
scrollbarWidth: 'thin',
'.dark &': {
'&::-webkit-scrollbar': {
background: 'rgba(0,0,0,.0)'
},
"&::-webkit-scrollbar-gutter": "stable",
"&::-webkit-scrollbar-thumb": {
backgroundColor: "rgba(255,255,255,.2)",
outline: "0px",
borderRadius: "9999px",
},
scrollbarColor: "rgba(255,255,255,.2) rgba(0,0,0,0)",
scrollbarGutter: "stable",
scrollbarWidth: "thin",
'&::-webkit-scrollbar-gutter': 'stable',
'&::-webkit-scrollbar-thumb': {
backgroundColor: 'rgba(255,255,255,.2)',
outline: '0px',
borderRadius: '9999px'
},
scrollbarColor: 'rgba(255,255,255,.2) rgba(0,0,0,0)',
scrollbarGutter: 'stable',
scrollbarWidth: 'thin'
}
}}
onWheelCapture={e => {
if (scrollRef.current) {
scrollRef.current.scrollLeft += e.deltaY;
scrollRef.current.scrollLeft += e.deltaY
}
}}
>
@@ -138,37 +126,35 @@ const EditorNavigation = ({ renderNav }: { renderNav?: () => ReactNode }) => {
</Flex>
<Flex
css={{
py: "$3",
backgroundColor: "$mauve2",
zIndex: 1,
py: '$3',
backgroundColor: '$mauve2',
zIndex: 1
}}
>
<Container
css={{ width: "unset", display: "flex", alignItems: "center" }}
>
{status === "authenticated" ? (
<Container css={{ width: 'unset', display: 'flex', alignItems: 'center' }}>
{status === 'authenticated' ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Box
css={{
display: "flex",
borderRadius: "$full",
overflow: "hidden",
width: "$6",
height: "$6",
boxShadow: "0px 0px 0px 1px $colors$mauve11",
position: "relative",
mr: "$3",
"@hover": {
"&:hover": {
cursor: "pointer",
boxShadow: "0px 0px 0px 1px $colors$mauve12",
},
},
display: 'flex',
borderRadius: '$full',
overflow: 'hidden',
width: '$6',
height: '$6',
boxShadow: '0px 0px 0px 1px $colors$mauve11',
position: 'relative',
mr: '$3',
'@hover': {
'&:hover': {
cursor: 'pointer',
boxShadow: '0px 0px 0px 1px $colors$mauve12'
}
}
}}
>
<Image
src={session?.user?.image || ""}
src={session?.user?.image || ''}
width="30px"
height="30px"
objectFit="cover"
@@ -178,21 +164,16 @@ const EditorNavigation = ({ renderNav }: { renderNav?: () => ReactNode }) => {
</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
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => signOut({ callbackUrl: "/" })}>
<DropdownMenuItem onClick={() => signOut({ callbackUrl: '/' })}>
<SignOut size="16px" /> Log out
</DropdownMenuItem>
@@ -200,48 +181,43 @@ const EditorNavigation = ({ renderNav }: { renderNav?: () => ReactNode }) => {
</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>
)}
<Stack
css={{
display: "inline-flex",
marginLeft: "auto",
display: 'inline-flex',
marginLeft: 'auto',
flexShrink: 0,
gap: "$0",
borderRadius: "$sm",
boxShadow: "inset 0px 0px 0px 1px $colors$mauve10",
gap: '$0',
borderRadius: '$sm',
boxShadow: 'inset 0px 0px 0px 1px $colors$mauve10',
zIndex: 9,
position: "relative",
position: 'relative',
button: {
borderRadius: 0,
px: "$2",
alignSelf: "flex-start",
boxShadow: "none",
px: '$2',
alignSelf: 'flex-start',
boxShadow: 'none'
},
"button:not(:first-child):not(:last-child)": {
'button:not(:first-child):not(:last-child)': {
borderRight: 0,
borderLeft: 0,
},
"button:first-child": {
borderTopLeftRadius: "$sm",
borderBottomLeftRadius: "$sm",
},
"button:last-child": {
borderTopRightRadius: "$sm",
borderBottomRightRadius: "$sm",
boxShadow: "inset 0px 0px 0px 1px $colors$mauve10",
"&:hover": {
boxShadow: "inset 0px 0px 0px 1px $colors$mauve12",
borderLeft: 0
},
'button:first-child': {
borderTopLeftRadius: '$sm',
borderBottomLeftRadius: '$sm'
},
'button:last-child': {
borderTopRightRadius: '$sm',
borderBottomRightRadius: '$sm',
boxShadow: 'inset 0px 0px 0px 1px $colors$mauve10',
'&:hover': {
boxShadow: 'inset 0px 0px 0px 1px $colors$mauve12'
}
}
}}
>
<Tooltip content="Download as ZIP">
@@ -250,7 +226,7 @@ const EditorNavigation = ({ renderNav }: { renderNav?: () => ReactNode }) => {
onClick={downloadAsZip}
outline
size="sm"
css={{ alignItems: "center" }}
css={{ alignItems: 'center' }}
>
<DownloadSimple size="16px" />
</Button>
@@ -259,12 +235,10 @@ const EditorNavigation = ({ renderNav }: { renderNav?: () => ReactNode }) => {
<Button
outline
size="sm"
css={{ alignItems: "center" }}
css={{ alignItems: 'center' }}
onClick={() => {
navigator.clipboard.writeText(
`${window.location.origin}/develop/${snap.gistId}`
);
toast.success("Copied share link to clipboard!");
navigator.clipboard.writeText(`${window.location.origin}/develop/${snap.gistId}`)
toast.success('Copied share link to clipboard!')
}}
>
<Share size="16px" />
@@ -274,9 +248,9 @@ const EditorNavigation = ({ renderNav }: { renderNav?: () => ReactNode }) => {
content={
session && session.user
? snap.gistOwner === session?.user.username
? "Sync to Gist"
: "Create as a new Gist"
: "You need to be logged in to sync with Gist"
? 'Sync to Gist'
: 'Create as a new Gist'
: 'You need to be logged in to sync with Gist'
}
>
<Button
@@ -284,15 +258,15 @@ const EditorNavigation = ({ renderNav }: { renderNav?: () => ReactNode }) => {
size="sm"
isDisabled={!session || !session.user}
isLoading={snap.gistLoading}
css={{ alignItems: "center" }}
css={{ alignItems: 'center' }}
onClick={() => {
if (!session || !session.user) {
return;
return
}
if (snap.gistOwner === session?.user.username) {
syncToGist(session);
syncToGist(session)
} else {
showNewGistAlert();
showNewGistAlert()
}
}}
>
@@ -311,38 +285,33 @@ const EditorNavigation = ({ renderNav }: { renderNav?: () => ReactNode }) => {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
disabled={snap.zipLoading}
onClick={downloadAsZip}
>
<DropdownMenuItem disabled={snap.zipLoading} onClick={downloadAsZip}>
<DownloadSimple size="16px" /> Download as ZIP
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
navigator.clipboard.writeText(
`${window.location.origin}/develop/${snap.gistId}`
);
toast.success("Copied share link to clipboard!");
)
toast.success('Copied share link to clipboard!')
}}
>
<Share size="16px" />
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);
syncToGist(session)
}}
>
<CloudArrowUp size="16px" /> Push to Gist
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
disabled={status !== "authenticated"}
disabled={status !== 'authenticated'}
onClick={() => {
showNewGistAlert();
showNewGistAlert()
}}
>
<FilePlus size="16px" /> Create as a new Gist
@@ -357,9 +326,7 @@ const EditorNavigation = ({ renderNav }: { renderNav?: () => ReactNode }) => {
</DropdownMenu>
</Stack>
{popup && !session ? (
<NewWindow center="parent" url="/sign-in" />
) : null}
{popup && !session ? <NewWindow center="parent" url="/sign-in" /> : null}
</Container>
</Flex>
@@ -380,39 +347,33 @@ const EditorNavigation = ({ renderNav }: { renderNav?: () => ReactNode }) => {
onChange={e =>
setEditorSettings(curr => ({
...curr,
tabSize: Number(e.target.value),
tabSize: Number(e.target.value)
}))
}
/>
</DialogDescription>
<Flex css={{ marginTop: 25, justifyContent: "flex-end", gap: "$3" }}>
<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>
</Flex>
<DialogClose asChild>
<Box css={{ position: "absolute", top: "$3", right: "$3" }}>
<Box css={{ position: 'absolute', top: '$3', right: '$3' }}>
<X size="20px" />
</Box>
</DialogClose>
</DialogContent>
</Dialog>
</Flex>
);
};
)
}
export default EditorNavigation;
export default EditorNavigation

View File

@@ -1,53 +1,53 @@
import { styled } from "../stitches.config";
import Box from "./Box";
import { styled } from '../stitches.config'
import Box from './Box'
const Flex = styled(Box, {
display: "flex",
display: 'flex',
variants: {
row: {
true: {
flexDirection: "row",
},
flexDirection: 'row'
}
},
column: {
true: {
flexDirection: "column",
},
flexDirection: 'column'
}
},
fluid: {
true: {
width: "100%",
},
width: '100%'
}
},
align: {
start: {
alignItems: "start",
alignItems: 'start'
},
center: {
alignItems: "center",
alignItems: 'center'
},
end: {
alignItems: "end",
},
alignItems: 'end'
}
},
justify: {
start: {
justifyContent: "start",
justifyContent: 'start'
},
center: {
justifyContent: "center",
justifyContent: 'center'
},
end: {
justifyContent: "end",
justifyContent: 'end'
},
"space-between": {
justifyContent: "space-between",
'space-between': {
justifyContent: 'space-between'
},
"space-around": {
justifyContent: "space-around",
},
},
},
});
'space-around': {
justifyContent: 'space-around'
}
}
}
})
export default Flex;
export default Flex

View File

@@ -1,16 +1,16 @@
import { styled } from "../stitches.config";
import { styled } from '../stitches.config'
const Heading = styled("span", {
fontFamily: "$heading",
lineHeight: "$heading",
fontWeight: "$heading",
const Heading = styled('span', {
fontFamily: '$heading',
lineHeight: '$heading',
fontWeight: '$heading',
variants: {
uppercase: {
true: {
textTransform: "uppercase",
},
},
},
});
textTransform: 'uppercase'
}
}
}
})
export default Heading;
export default Heading

View File

@@ -1,45 +1,43 @@
import React, { useEffect, useRef } from "react";
import { useSnapshot, ref } from "valtio";
import type monaco from "monaco-editor";
import { ArrowBendLeftUp } from "phosphor-react";
import { useTheme } from "next-themes";
import { useRouter } from "next/router";
import React, { useEffect, useRef } from 'react'
import { useSnapshot, ref } from 'valtio'
import type monaco from 'monaco-editor'
import { ArrowBendLeftUp } from 'phosphor-react'
import { useTheme } from 'next-themes'
import { useRouter } from 'next/router'
import Box from "./Box";
import Container from "./Container";
import { createNewFile, saveFile } from "../state/actions";
import { apiHeaderFiles } from "../state/constants";
import state from "../state";
import Box from './Box'
import Container from './Container'
import { createNewFile, saveFile } from '../state/actions'
import { apiHeaderFiles } from '../state/constants'
import state from '../state'
import EditorNavigation from "./EditorNavigation";
import Text from "./Text";
import { MonacoServices } from "@codingame/monaco-languageclient";
import { createLanguageClient, createWebSocket } from "../utils/languageClient";
import { listen } from "@codingame/monaco-jsonrpc";
import ReconnectingWebSocket from "reconnecting-websocket";
import EditorNavigation from './EditorNavigation'
import Text from './Text'
import { MonacoServices } from '@codingame/monaco-languageclient'
import { createLanguageClient, createWebSocket } from '../utils/languageClient'
import { listen } from '@codingame/monaco-jsonrpc'
import ReconnectingWebSocket from 'reconnecting-websocket'
import docs from "../xrpl-hooks-docs/docs";
import Monaco from "./Monaco";
import { saveAllFiles } from "../state/actions/saveFile";
import { Tab, Tabs } from "./Tabs";
import { renameFile } from "../state/actions/createNewFile";
import docs from '../xrpl-hooks-docs/docs'
import Monaco from './Monaco'
import { saveAllFiles } from '../state/actions/saveFile'
import { Tab, Tabs } from './Tabs'
import { renameFile } from '../state/actions/createNewFile'
const checkWritable = (filename?: string): boolean => {
if (apiHeaderFiles.find(file => file === filename)) {
return false;
return false
}
return true;
};
return true
}
const validateWritability = (
editor: monaco.editor.IStandaloneCodeEditor
) => {
const filename = editor.getModel()?.uri.path.split("/").pop();
const isWritable = checkWritable(filename);
editor.updateOptions({ readOnly: !isWritable });
};
const validateWritability = (editor: monaco.editor.IStandaloneCodeEditor) => {
const filename = editor.getModel()?.uri.path.split('/').pop()
const isWritable = checkWritable(filename)
editor.updateOptions({ readOnly: !isWritable })
}
let decorations: { [key: string]: string[] } = {};
let decorations: { [key: string]: string[] } = {}
const setMarkers = (monacoE: typeof monaco) => {
// Get all the markers that are active at the moment,
@@ -50,10 +48,10 @@ const setMarkers = (monacoE: typeof monaco) => {
// Filter out the markers that are hooks specific
.filter(
marker =>
typeof marker?.code === "string" &&
typeof marker?.code === 'string' &&
// Take only markers that starts with "hooks-"
marker?.code?.includes("hooks-")
);
marker?.code?.includes('hooks-')
)
// Get the active model (aka active file you're editing)
// const model = monacoE.editor?.getModel(
@@ -62,15 +60,13 @@ const setMarkers = (monacoE: typeof monaco) => {
// console.log(state.active);
// Add decoration (aka extra hoverMessages) to markers in the
// exact same range (location) where the markers are
const models = monacoE.editor.getModels();
const models = monacoE.editor.getModels()
models.forEach(model => {
decorations[model.id] = model?.deltaDecorations(
decorations?.[model.id] || [],
markers
.filter(marker =>
marker?.resource.path
.split("/")
.includes(`${state.files?.[state.active]?.name}`)
marker?.resource.path.split('/').includes(`${state.files?.[state.active]?.name}`)
)
.map(marker => ({
range: new monacoE.Range(
@@ -86,47 +82,45 @@ const setMarkers = (monacoE: typeof monaco) => {
// /xrpl-hooks-docs/xrpl-hooks-docs-files.json file
// which was generated from rst files
(typeof marker.code === "string" &&
docs[marker?.code]?.toString()) ||
"",
(typeof marker.code === 'string' && docs[marker?.code]?.toString()) || '',
supportHtml: true,
isTrusted: true,
},
},
isTrusted: true
}
}
}))
);
});
};
)
})
}
const HooksEditor = () => {
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>();
const monacoRef = useRef<typeof monaco>();
const subscriptionRef = useRef<ReconnectingWebSocket | null>(null);
const snap = useSnapshot(state);
const router = useRouter();
const { theme } = useTheme();
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>()
const monacoRef = useRef<typeof monaco>()
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]);
if (editorRef.current) validateWritability(editorRef.current)
}, [snap.active])
useEffect(() => {
return () => {
subscriptionRef?.current?.close();
};
}, []);
subscriptionRef?.current?.close()
}
}, [])
useEffect(() => {
if (monacoRef.current) {
setMarkers(monacoRef.current);
setMarkers(monacoRef.current)
}
}, [snap.active]);
}, [snap.active])
useEffect(() => {
return () => {
saveAllFiles();
};
}, []);
saveAllFiles()
}
}, [])
const file = snap.files[snap.active];
const file = snap.files[snap.active]
const renderNav = () => (
<Tabs
@@ -136,28 +130,27 @@ const HooksEditor = () => {
extensionRequired
onCreateNewTab={createNewFile}
onCloseTab={idx => state.files.splice(idx, 1)}
onRenameTab={(idx, nwName, oldName = "") => renameFile(oldName, nwName)}
onRenameTab={(idx, nwName, oldName = '') => renameFile(oldName, nwName)}
headerExtraValidation={{
regex: /^[A-Za-z0-9_-]+[.][A-Za-z0-9]{1,4}$/g,
error:
'Filename can contain only characters from a-z, A-Z, 0-9, "_" and "-"',
error: 'Filename can contain only characters from a-z, A-Z, 0-9, "_" and "-"'
}}
>
{snap.files.map((file, index) => {
return <Tab key={file.name} header={file.name} renameDisabled={!checkWritable(file.name)} />;
return <Tab key={file.name} header={file.name} renameDisabled={!checkWritable(file.name)} />
})}
</Tabs>
);
)
return (
<Box
css={{
flex: 1,
flexShrink: 1,
display: "flex",
position: "relative",
flexDirection: "column",
backgroundColor: "$mauve2",
width: "100%",
display: 'flex',
position: 'relative',
flexDirection: 'column',
backgroundColor: '$mauve2',
width: '100%'
}}
>
<EditorNavigation renderNav={renderNav} />
@@ -177,39 +170,39 @@ const HooksEditor = () => {
file.language,
monaco.Uri.parse(`file:///work/c/${file.name}`)
)
);
)
}
// create the web socket
if (!subscriptionRef.current) {
monaco.languages.register({
id: "c",
extensions: [".c", ".h"],
aliases: ["C", "c", "H", "h"],
mimetypes: ["text/plain"],
});
MonacoServices.install(monaco);
id: 'c',
extensions: ['.c', '.h'],
aliases: ['C', 'c', 'H', 'h'],
mimetypes: ['text/plain']
})
MonacoServices.install(monaco)
const webSocket = createWebSocket(
process.env.NEXT_PUBLIC_LANGUAGE_SERVER_API_ENDPOINT || ""
);
subscriptionRef.current = webSocket;
process.env.NEXT_PUBLIC_LANGUAGE_SERVER_API_ENDPOINT || ''
)
subscriptionRef.current = webSocket
// listen when the web socket is opened
listen({
webSocket: webSocket as WebSocket,
onConnection: connection => {
// create and start the language client
const languageClient = createLanguageClient(connection);
const disposable = languageClient.start();
const languageClient = createLanguageClient(connection)
const disposable = languageClient.start()
connection.onClose(() => {
try {
disposable.dispose();
disposable.dispose()
} catch (err) {
console.log("err", err);
console.log('err', err)
}
});
},
});
})
}
})
}
// editor.updateOptions({
@@ -219,75 +212,68 @@ const HooksEditor = () => {
// ...snap.editorSettings,
// });
if (!state.editorCtx) {
state.editorCtx = ref(monaco.editor);
state.editorCtx = ref(monaco.editor)
}
}}
onMount={(editor, monaco) => {
editorRef.current = editor;
monacoRef.current = monaco;
editorRef.current = editor
monacoRef.current = monaco
editor.updateOptions({
glyphMargin: true,
lightbulb: {
enabled: true,
},
});
editor.addCommand(
monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS,
() => {
saveFile();
enabled: true
}
);
})
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
saveFile()
})
// When the markers (errors/warnings from clangd language server) change
// Lets improve the markers by adding extra content to them from related
// md files
monaco.editor.onDidChangeMarkers(() => {
if (monacoRef.current) {
setMarkers(monacoRef.current);
setMarkers(monacoRef.current)
}
});
})
// Hacky way to hide Peek menu
editor.onContextMenu(e => {
const host =
document.querySelector<HTMLElement>(".shadow-root-host");
const host = document.querySelector<HTMLElement>('.shadow-root-host')
const contextMenuItems =
host?.shadowRoot?.querySelectorAll("li.action-item");
const contextMenuItems = host?.shadowRoot?.querySelectorAll('li.action-item')
contextMenuItems?.forEach(k => {
// If menu item contains "Peek" lets hide it
if (k.querySelector(".action-label")?.textContent === "Peek") {
if (k.querySelector('.action-label')?.textContent === 'Peek') {
// @ts-expect-error
k["style"].display = "none";
k['style'].display = 'none'
}
});
});
})
})
validateWritability(editor);
validateWritability(editor)
}}
theme={theme === "dark" ? "dark" : "light"}
theme={theme === 'dark' ? 'dark' : 'light'}
/>
) : (
<Container>
{!snap.loading && router.isReady && (
<Box
css={{
flexDirection: "row",
width: "$spaces$wide",
gap: "$3",
display: "inline-flex",
flexDirection: 'row',
width: '$spaces$wide',
gap: '$3',
display: 'inline-flex'
}}
>
<Box css={{ display: "inline-flex", pl: "35px" }}>
<Box css={{ display: 'inline-flex', pl: '35px' }}>
<ArrowBendLeftUp size={30} />
</Box>
<Box
css={{ pl: "0px", pt: "15px", flex: 1, display: "inline-flex" }}
>
<Box css={{ pl: '0px', pt: '15px', flex: 1, display: 'inline-flex' }}>
<Text
css={{
fontSize: "14px",
maxWidth: "220px",
fontFamily: "$monospace",
fontSize: '14px',
maxWidth: '220px',
fontFamily: '$monospace'
}}
>
Click the link above to create your file
@@ -298,7 +284,7 @@ const HooksEditor = () => {
</Container>
)}
</Box>
);
};
)
}
export default HooksEditor;
export default HooksEditor

View File

@@ -1,165 +1,161 @@
import React from "react";
import { styled } from "../stitches.config";
import * as LabelPrim from '@radix-ui/react-label';
import React from 'react'
import { styled } from '../stitches.config'
import * as LabelPrim from '@radix-ui/react-label'
export const Input = styled("input", {
export const Input = styled('input', {
// Reset
appearance: "none",
borderWidth: "0",
boxSizing: "border-box",
fontFamily: "inherit",
outline: "none",
width: "100%",
flex: "1",
backgroundColor: "$mauve4",
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
borderRadius: "$sm",
px: "$2",
fontSize: "$md",
appearance: 'none',
borderWidth: '0',
boxSizing: 'border-box',
fontFamily: 'inherit',
outline: 'none',
width: '100%',
flex: '1',
backgroundColor: '$mauve4',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '$sm',
px: '$2',
fontSize: '$md',
lineHeight: 1,
color: "$mauve12",
color: '$mauve12',
boxShadow: `0 0 0 1px $colors$mauve8`,
height: 35,
WebkitTapHighlightColor: "rgba(0,0,0,0)",
"&::before": {
boxSizing: "border-box",
WebkitTapHighlightColor: 'rgba(0,0,0,0)',
'&::before': {
boxSizing: 'border-box'
},
"&::after": {
boxSizing: "border-box",
'&::after': {
boxSizing: 'border-box'
},
fontVariantNumeric: "tabular-nums",
fontVariantNumeric: 'tabular-nums',
"&:-webkit-autofill": {
boxShadow: "inset 0 0 0 1px $colors$blue6, inset 0 0 0 100px $colors$blue3",
'&:-webkit-autofill': {
boxShadow: 'inset 0 0 0 1px $colors$blue6, inset 0 0 0 100px $colors$blue3'
},
"&:-webkit-autofill::first-line": {
fontFamily: "$untitled",
color: "$mauve12",
'&:-webkit-autofill::first-line': {
fontFamily: '$untitled',
color: '$mauve12'
},
"&:focus": {
boxShadow: `0 0 0 1px $colors$mauve10`,
"&:-webkit-autofill": {
'&:focus': {
boxShadow: `0 0 0 1px $colors$mauve10`,
'&:-webkit-autofill': {
boxShadow: `0 0 0 1px $colors$mauve10`
}
},
'&::placeholder': {
color: '$mauve9'
},
"&::placeholder": {
color: "$mauve9",
'&:disabled': {
pointerEvents: 'none',
backgroundColor: '$mauve2',
color: '$mauve8',
cursor: 'not-allowed',
'&::placeholder': {
color: '$mauve7'
}
},
"&:disabled": {
pointerEvents: "none",
backgroundColor: "$mauve2",
color: "$mauve8",
cursor: "not-allowed",
"&::placeholder": {
color: "$mauve7",
},
},
"&:read-only": {
backgroundColor: "$mauve2",
color: "$text",
'&:read-only': {
backgroundColor: '$mauve2',
color: '$text',
opacity: 0.8,
"&:focus": {
boxShadow: "inset 0px 0px 0px 1px $colors$mauve7",
},
'&:focus': {
boxShadow: 'inset 0px 0px 0px 1px $colors$mauve7'
}
},
variants: {
size: {
sm: {
height: "$5",
fontSize: "$1",
lineHeight: "$sizes$4",
"&:-webkit-autofill::first-line": {
fontSize: "$1",
},
height: '$5',
fontSize: '$1',
lineHeight: '$sizes$4',
'&:-webkit-autofill::first-line': {
fontSize: '$1'
}
},
md: {
height: "$8",
fontSize: "$1",
lineHeight: "$sizes$5",
"&:-webkit-autofill::first-line": {
fontSize: "$1",
},
height: '$8',
fontSize: '$1',
lineHeight: '$sizes$5',
'&:-webkit-autofill::first-line': {
fontSize: '$1'
}
},
lg: {
height: "$12",
fontSize: "$2",
lineHeight: "$sizes$6",
"&:-webkit-autofill::first-line": {
fontSize: "$3",
},
},
height: '$12',
fontSize: '$2',
lineHeight: '$sizes$6',
'&:-webkit-autofill::first-line': {
fontSize: '$3'
}
}
},
variant: {
ghost: {
boxShadow: "none",
backgroundColor: "transparent",
"@hover": {
"&:hover": {
boxShadow: "inset 0 0 0 1px $colors$mauve7",
boxShadow: 'none',
backgroundColor: 'transparent',
'@hover': {
'&:hover': {
boxShadow: 'inset 0 0 0 1px $colors$mauve7'
}
},
'&:focus': {
backgroundColor: '$loContrast',
boxShadow: `0 0 0 1px $colors$mauve10`
},
"&:focus": {
backgroundColor: "$loContrast",
boxShadow: `0 0 0 1px $colors$mauve10`,
},
"&:disabled": {
backgroundColor: "transparent",
},
"&:read-only": {
backgroundColor: "transparent",
'&:disabled': {
backgroundColor: 'transparent'
},
'&:read-only': {
backgroundColor: 'transparent'
}
},
deep: {
backgroundColor: "$deep",
boxShadow: "none",
},
backgroundColor: '$deep',
boxShadow: 'none'
}
},
state: {
invalid: {
boxShadow: "inset 0 0 0 1px $colors$crimson7",
"&:focus": {
boxShadow:
"inset 0px 0px 0px 1px $colors$crimson8, 0px 0px 0px 1px $colors$crimson8",
},
boxShadow: 'inset 0 0 0 1px $colors$crimson7',
'&:focus': {
boxShadow: 'inset 0px 0px 0px 1px $colors$crimson8, 0px 0px 0px 1px $colors$crimson8'
}
},
valid: {
boxShadow: "inset 0 0 0 1px $colors$grass7",
"&:focus": {
boxShadow:
"inset 0px 0px 0px 1px $colors$grass8, 0px 0px 0px 1px $colors$grass8",
},
},
boxShadow: 'inset 0 0 0 1px $colors$grass7',
'&:focus': {
boxShadow: 'inset 0px 0px 0px 1px $colors$grass8, 0px 0px 0px 1px $colors$grass8'
}
}
},
cursor: {
default: {
cursor: "default",
"&:focus": {
cursor: "text",
},
cursor: 'default',
'&:focus': {
cursor: 'text'
}
},
text: {
cursor: "text",
},
},
cursor: 'text'
}
}
},
defaultVariants: {
size: "md",
},
});
size: 'md'
}
})
// eslint-disable-next-line react/display-name
const ReffedInput = React.forwardRef<
HTMLInputElement,
React.ComponentProps<typeof Input>
>((props, ref) => <Input {...props} ref={ref} />);
export default ReffedInput;
const ReffedInput = React.forwardRef<HTMLInputElement, React.ComponentProps<typeof Input>>(
(props, ref) => <Input {...props} ref={ref} />
)
export default ReffedInput
const LabelRoot = (props: LabelPrim.LabelProps) => <LabelPrim.Root {...props} />

View File

@@ -1,8 +1,8 @@
import { styled } from "../stitches.config";
import { styled } from '../stitches.config'
const StyledLink = styled("a", {
color: "CurrentColor",
textDecoration: "underline",
const StyledLink = styled('a', {
color: 'CurrentColor',
textDecoration: 'underline',
cursor: 'pointer',
variants: {
highlighted: {
@@ -11,6 +11,6 @@ const StyledLink = styled("a", {
}
}
}
});
})
export default StyledLink;
export default StyledLink

View File

@@ -1,30 +1,23 @@
import {
useRef,
useLayoutEffect,
ReactNode,
FC,
useState,
useCallback,
} from "react";
import { IconProps, Notepad, Prohibit } from "phosphor-react";
import useStayScrolled from "react-stay-scrolled";
import NextLink from "next/link";
import { useRef, useLayoutEffect, ReactNode, FC, useState, useCallback } from 'react'
import { IconProps, Notepad, Prohibit } from 'phosphor-react'
import useStayScrolled from 'react-stay-scrolled'
import NextLink from 'next/link'
import Container from "./Container";
import LogText from "./LogText";
import state, { ILog } from "../state";
import { Pre, Link, Heading, Button, Text, Flex, Box } from ".";
import regexifyString from "regexify-string";
import { useSnapshot } from "valtio";
import { AccountDialog } from "./Accounts";
import Container from './Container'
import LogText from './LogText'
import state, { ILog } from '../state'
import { Pre, Link, Heading, Button, Text, Flex, Box } from '.'
import regexifyString from 'regexify-string'
import { useSnapshot } from 'valtio'
import { AccountDialog } from './Accounts'
interface ILogBox {
title: string;
clearLog?: () => void;
logs: ILog[];
renderNav?: () => ReactNode;
enhanced?: boolean;
Icon?: FC<IconProps>;
title: string
clearLog?: () => void
logs: ILog[]
renderNav?: () => ReactNode
enhanced?: boolean
Icon?: FC<IconProps>
}
const LogBox: FC<ILogBox> = ({
@@ -34,40 +27,40 @@ const LogBox: FC<ILogBox> = ({
children,
renderNav,
enhanced,
Icon = Notepad,
Icon = Notepad
}) => {
const logRef = useRef<HTMLPreElement>(null);
const { stayScrolled /*, scrollBottom*/ } = useStayScrolled(logRef);
const logRef = useRef<HTMLPreElement>(null)
const { stayScrolled /*, scrollBottom*/ } = useStayScrolled(logRef)
useLayoutEffect(() => {
stayScrolled();
}, [stayScrolled, logs]);
stayScrolled()
}, [stayScrolled, logs])
return (
<Flex
as="div"
css={{
display: "flex",
borderTop: "1px solid $mauve6",
background: "$mauve1",
position: "relative",
display: 'flex',
borderTop: '1px solid $mauve6',
background: '$mauve1',
position: 'relative',
flex: 1,
height: "100%",
height: '100%'
}}
>
<Container
css={{
px: 0,
height: "100%",
height: '100%'
}}
>
<Flex
fluid
css={{
height: "48px",
alignItems: "center",
fontSize: "$sm",
fontWeight: 300,
height: '48px',
alignItems: 'center',
fontSize: '$sm',
fontWeight: 300
}}
>
<Heading
@@ -75,13 +68,13 @@ const LogBox: FC<ILogBox> = ({
css={{
fontWeight: 300,
m: 0,
fontSize: "11px",
color: "$mauve12",
px: "$3",
textTransform: "uppercase",
alignItems: "center",
display: "inline-flex",
gap: "$3",
fontSize: '11px',
color: '$mauve12',
px: '$3',
textTransform: 'uppercase',
alignItems: 'center',
display: 'inline-flex',
gap: '$3'
}}
>
<Icon size="15px" /> <Text css={{ lineHeight: 1 }}>{title}</Text>
@@ -95,7 +88,7 @@ const LogBox: FC<ILogBox> = ({
>
{renderNav?.()}
</Flex>
<Flex css={{ ml: "auto", gap: "$3", marginRight: "$3" }}>
<Flex css={{ ml: 'auto', gap: '$3', marginRight: '$3' }}>
{clearLog && (
<Button ghost size="xs" onClick={clearLog}>
<Prohibit size="14px" />
@@ -110,17 +103,17 @@ const LogBox: FC<ILogBox> = ({
css={{
margin: 0,
// display: "inline-block",
display: "flex",
flexDirection: "column",
width: "100%",
height: "calc(100% - 48px)", // 100% minus the logbox header height
overflowY: "auto",
fontSize: "13px",
fontWeight: "$body",
fontFamily: "$monospace",
px: "$3",
pb: "$2",
whiteSpace: "normal",
display: 'flex',
flexDirection: 'column',
width: '100%',
height: 'calc(100% - 48px)', // 100% minus the logbox header height
overflowY: 'auto',
fontSize: '13px',
fontWeight: '$body',
fontFamily: '$monospace',
px: '$3',
pb: '$2',
whiteSpace: 'normal'
}}
>
{logs?.map((log, index) => (
@@ -128,13 +121,13 @@ const LogBox: FC<ILogBox> = ({
as="span"
key={log.type + index}
css={{
"@hover": {
"&:hover": {
backgroundColor: enhanced ? "$backgroundAlt" : undefined,
'@hover': {
'&:hover': {
backgroundColor: enhanced ? '$backgroundAlt' : undefined
}
},
},
p: enhanced ? "$1" : undefined,
my: enhanced ? "$1" : undefined,
p: enhanced ? '$1' : undefined,
my: enhanced ? '$1' : undefined
}}
>
<Log {...log} />
@@ -144,8 +137,8 @@ const LogBox: FC<ILogBox> = ({
</Box>
</Container>
</Flex>
);
};
)
}
export const Log: FC<ILog> = ({
type,
@@ -154,21 +147,21 @@ export const Log: FC<ILog> = ({
link,
linkText,
defaultCollapsed,
jsonData: _jsonData,
jsonData: _jsonData
}) => {
const [expanded, setExpanded] = useState(!defaultCollapsed);
const { accounts } = useSnapshot(state);
const [dialogAccount, setDialogAccount] = useState<string | null>(null);
const [expanded, setExpanded] = useState(!defaultCollapsed)
const { accounts } = useSnapshot(state)
const [dialogAccount, setDialogAccount] = useState<string | null>(null)
const enrichAccounts = useCallback(
(str?: string): ReactNode => {
if (!str || !accounts.length) return str;
if (!str || !accounts.length) return str
const pattern = `(${accounts.map(acc => acc.address).join("|")})`;
const pattern = `(${accounts.map(acc => acc.address).join('|')})`
const res = regexifyString({
pattern: new RegExp(pattern, "gim"),
pattern: new RegExp(pattern, 'gim'),
decorator: (match, idx) => {
const name = accounts.find(acc => acc.address === match)?.name;
const name = accounts.find(acc => acc.address === match)?.name
return (
<Link
key={match + idx}
@@ -179,27 +172,27 @@ export const Log: FC<ILog> = ({
>
{name || match}
</Link>
);
)
},
input: str,
});
input: str
})
return <>{res}</>;
return <>{res}</>
},
[accounts]
);
)
let message: ReactNode;
let message: ReactNode
if (typeof _message === "string") {
_message = _message.trim().replace(/\n /gi, "\n");
if (_message) message = enrichAccounts(_message);
if (typeof _message === 'string') {
_message = _message.trim().replace(/\n /gi, '\n')
if (_message) message = enrichAccounts(_message)
else message = <Text muted>{'""'}</Text>
} else {
message = _message;
message = _message
}
const jsonData = enrichAccounts(_jsonData);
const jsonData = enrichAccounts(_jsonData)
return (
<>
@@ -210,7 +203,7 @@ export const Log: FC<ILog> = ({
<LogText variant={type}>
{timestring && (
<Text muted monospace>
{timestring}{" "}
{timestring}{' '}
</Text>
)}
<Pre>{message} </Pre>
@@ -221,14 +214,14 @@ export const Log: FC<ILog> = ({
)}
{jsonData && (
<Link onClick={() => setExpanded(!expanded)} as="a">
{expanded ? "Collapse" : "Expand"}
{expanded ? 'Collapse' : 'Expand'}
</Link>
)}
{expanded && jsonData && <Pre block>{jsonData}</Pre>}
</LogText>
<br />
</>
);
};
)
}
export default LogBox;
export default LogBox

View File

@@ -1,31 +1,31 @@
import { styled } from "../stitches.config";
import { styled } from '../stitches.config'
const Text = styled("span", {
fontFamily: "$monospace",
lineHeight: "$body",
color: "$text",
wordWrap: "break-word",
const Text = styled('span', {
fontFamily: '$monospace',
lineHeight: '$body',
color: '$text',
wordWrap: 'break-word',
variants: {
variant: {
log: {
color: "$text",
color: '$text'
},
warning: {
color: "$warning",
color: '$warning'
},
error: {
color: "$error",
color: '$error'
},
success: {
color: "$success",
},
color: '$success'
}
},
capitalize: {
true: {
textTransform: "capitalize",
},
},
},
});
textTransform: 'capitalize'
}
}
}
})
export default Text;
export default Text

View File

@@ -1,21 +1,15 @@
import { styled } from "../stitches.config";
import { styled } from '../stitches.config'
const SVG = styled("svg", {
"& #path": {
fill: "$accent",
},
});
function Logo({
width,
height,
}: {
width?: string | number;
height?: string | number;
}) {
const SVG = styled('svg', {
'& #path': {
fill: '$accent'
}
})
function Logo({ width, height }: { width?: string | number; height?: string | number }) {
return (
<SVG
width={width || "1.1em"}
height={height || "1.1em"}
width={width || '1.1em'}
height={height || '1.1em'}
viewBox="0 0 294 283"
fill="none"
xmlns="http://www.w3.org/2000/svg"
@@ -28,7 +22,7 @@ function Logo({
fill="#9D2DFF"
/>
</SVG>
);
)
}
export default Logo;
export default Logo

View File

@@ -1,31 +1,31 @@
import Editor, { loader, EditorProps, Monaco } from "@monaco-editor/react";
import { CSS } from "@stitches/react";
import type monaco from "monaco-editor";
import { useTheme } from "next-themes";
import { FC, MutableRefObject, ReactNode } from "react";
import { Flex } from ".";
import dark from "../theme/editor/amy.json";
import light from "../theme/editor/xcode_default.json";
import Editor, { loader, EditorProps, Monaco } from '@monaco-editor/react'
import { CSS } from '@stitches/react'
import type monaco from 'monaco-editor'
import { useTheme } from 'next-themes'
import { FC, MutableRefObject, ReactNode } from 'react'
import { Flex } from '.'
import dark from '../theme/editor/amy.json'
import light from '../theme/editor/xcode_default.json'
export type MonacoProps = EditorProps & {
id?: string;
rootProps?: { css: CSS } & Record<string, any>;
overlay?: ReactNode;
editorRef?: MutableRefObject<monaco.editor.IStandaloneCodeEditor>;
monacoRef?: MutableRefObject<typeof monaco>;
};
id?: string
rootProps?: { css: CSS } & Record<string, any>
overlay?: ReactNode
editorRef?: MutableRefObject<monaco.editor.IStandaloneCodeEditor>
monacoRef?: MutableRefObject<typeof monaco>
}
loader.config({
paths: {
vs: "https://cdn.jsdelivr.net/npm/monaco-editor@0.30.1/min/vs",
},
});
vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.30.1/min/vs'
}
})
const Monaco: FC<MonacoProps> = ({
id,
path = `file:///${id}`,
className = id,
language = "json",
language = 'json',
overlay,
editorRef,
monacoRef,
@@ -33,20 +33,20 @@ const Monaco: FC<MonacoProps> = ({
rootProps,
...rest
}) => {
const { theme } = useTheme();
const { theme } = useTheme()
const setTheme = (monaco: Monaco) => {
monaco.editor.defineTheme("dark", dark as any);
monaco.editor.defineTheme("light", light as any);
};
monaco.editor.defineTheme('dark', dark as any)
monaco.editor.defineTheme('light', light as any)
}
return (
<Flex
fluid
column
{...rootProps}
css={{
position: "relative",
height: "100%",
...rootProps?.css,
position: 'relative',
height: '100%',
...rootProps?.css
}}
>
<Editor
@@ -54,22 +54,18 @@ const Monaco: FC<MonacoProps> = ({
language={language}
path={path}
beforeMount={monaco => {
beforeMount?.(monaco);
beforeMount?.(monaco)
setTheme(monaco);
setTheme(monaco)
}}
theme={theme === "dark" ? "dark" : "light"}
theme={theme === 'dark' ? 'dark' : 'light'}
{...rest}
/>
{overlay && (
<Flex
css={{ position: "absolute", bottom: 0, right: 0, width: "100%" }}
>
{overlay}
</Flex>
<Flex css={{ position: 'absolute', bottom: 0, right: 0, width: '100%' }}>{overlay}</Flex>
)}
</Flex>
);
};
)
}
export default Monaco;
export default Monaco

View File

@@ -1,90 +1,90 @@
import React from "react";
import Link from "next/link";
import React from 'react'
import Link from 'next/link'
import { useSnapshot } from "valtio";
import { useRouter } from "next/router";
import { FolderOpen, X, ArrowUpRight, BookOpen } from "phosphor-react";
import { useSnapshot } from 'valtio'
import { useRouter } from 'next/router'
import { FolderOpen, X, ArrowUpRight, BookOpen } from 'phosphor-react'
import Stack from "./Stack";
import Logo from "./Logo";
import Button from "./Button";
import Flex from "./Flex";
import Container from "./Container";
import Box from "./Box";
import ThemeChanger from "./ThemeChanger";
import state from "../state";
import Heading from "./Heading";
import Text from "./Text";
import Spinner from "./Spinner";
import truncate from "../utils/truncate";
import ButtonGroup from "./ButtonGroup";
import Stack from './Stack'
import Logo from './Logo'
import Button from './Button'
import Flex from './Flex'
import Container from './Container'
import Box from './Box'
import ThemeChanger from './ThemeChanger'
import state from '../state'
import Heading from './Heading'
import Text from './Text'
import Spinner from './Spinner'
import truncate from '../utils/truncate'
import ButtonGroup from './ButtonGroup'
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogTitle,
DialogTrigger,
} from "./Dialog";
import PanelBox from "./PanelBox";
import { templateFileIds } from "../state/constants";
import { styled } from "../stitches.config";
DialogTrigger
} from './Dialog'
import PanelBox from './PanelBox'
import { templateFileIds } from '../state/constants'
import { styled } from '../stitches.config'
const ImageWrapper = styled(Flex, {
position: "relative",
mt: "$2",
mb: "$10",
position: 'relative',
mt: '$2',
mb: '$10',
svg: {
// fill: "red",
".angle": {
fill: "$text",
'.angle': {
fill: '$text'
},
":not(.angle)": {
stroke: "$text",
},
},
});
':not(.angle)': {
stroke: '$text'
}
}
})
const Navigation = () => {
const router = useRouter();
const snap = useSnapshot(state);
const slug = router.query?.slug;
const gistId = Array.isArray(slug) ? slug[0] : null;
const router = useRouter()
const snap = useSnapshot(state)
const slug = router.query?.slug
const gistId = Array.isArray(slug) ? slug[0] : null
return (
<Box
as="nav"
css={{
display: "flex",
backgroundColor: "$mauve1",
borderBottom: "1px solid $mauve6",
position: "relative",
display: 'flex',
backgroundColor: '$mauve1',
borderBottom: '1px solid $mauve6',
position: 'relative',
zIndex: 2003,
height: "60px",
height: '60px'
}}
>
<Container
css={{
display: "flex",
alignItems: "center",
display: 'flex',
alignItems: 'center'
}}
>
<Flex
css={{
flex: 1,
alignItems: "center",
borderRight: "1px solid $colors$mauve6",
py: "$3",
pr: "$4",
alignItems: 'center',
borderRight: '1px solid $colors$mauve6',
py: '$3',
pr: '$4'
}}
>
<Link href={gistId ? `/develop/${gistId}` : "/develop"} passHref>
<Link href={gistId ? `/develop/${gistId}` : '/develop'} passHref>
<Box
as="a"
css={{
display: "flex",
alignItems: "center",
color: "$textColor",
display: 'flex',
alignItems: 'center',
color: '$textColor'
}}
>
<Logo width="32px" height="32px" />
@@ -92,38 +92,30 @@ const Navigation = () => {
</Link>
<Flex
css={{
ml: "$5",
flexDirection: "column",
gap: "1px",
ml: '$5',
flexDirection: 'column',
gap: '1px'
}}
>
{snap.loading ? (
<Spinner />
) : (
<>
<Heading css={{ lineHeight: 1 }}>
{snap.files?.[0]?.name || "XRPL Hooks"}
</Heading>
<Text
css={{ fontSize: "$xs", color: "$mauve10", lineHeight: 1 }}
>
{snap.files.length > 0 ? "Gist: " : "Builder"}
<Heading css={{ lineHeight: 1 }}>{snap.files?.[0]?.name || 'XRPL Hooks'}</Heading>
<Text css={{ fontSize: '$xs', color: '$mauve10', lineHeight: 1 }}>
{snap.files.length > 0 ? 'Gist: ' : 'Builder'}
{snap.files.length > 0 && (
<Link
href={`https://gist.github.com/${snap.gistOwner || ""}/${
snap.gistId || ""
}`}
href={`https://gist.github.com/${snap.gistOwner || ''}/${snap.gistId || ''}`}
passHref
>
<Text
as="a"
target="_blank"
rel="noreferrer noopener"
css={{ color: "$mauve12" }}
css={{ color: '$mauve12' }}
>
{`${snap.gistOwner || "-"}/${truncate(
snap.gistId || ""
)}`}
{`${snap.gistOwner || '-'}/${truncate(snap.gistId || '')}`}
</Text>
</Link>
)}
@@ -132,11 +124,8 @@ const Navigation = () => {
)}
</Flex>
{router.isReady && (
<ButtonGroup css={{ marginLeft: "auto" }}>
<Dialog
open={snap.mainModalOpen}
onOpenChange={(open) => (state.mainModalOpen = open)}
>
<ButtonGroup css={{ marginLeft: 'auto' }}>
<Dialog open={snap.mainModalOpen} onOpenChange={open => (state.mainModalOpen = open)}>
<DialogTrigger asChild>
<Button outline>
<FolderOpen size="15px" />
@@ -144,51 +133,51 @@ const Navigation = () => {
</DialogTrigger>
<DialogContent
css={{
display: "flex",
maxWidth: "1080px",
width: "80vw",
maxHeight: "80%",
backgroundColor: "$mauve1 !important",
overflowY: "auto",
background: "black",
p: 0,
display: 'flex',
maxWidth: '1080px',
width: '80vw',
maxHeight: '80%',
backgroundColor: '$mauve1 !important',
overflowY: 'auto',
background: 'black',
p: 0
}}
>
<Flex
css={{
flexDirection: "column",
height: "100%",
"@md": {
flexDirection: "row",
height: "100%",
},
flexDirection: 'column',
height: '100%',
'@md': {
flexDirection: 'row',
height: '100%'
}
}}
>
<Flex
css={{
borderBottom: "1px solid $colors$mauve5",
width: "100%",
minWidth: "240px",
flexDirection: "column",
p: "$7",
backgroundColor: "$mauve2",
"@md": {
width: "30%",
maxWidth: "300px",
borderBottom: "0px",
borderRight: "1px solid $colors$mauve5",
},
borderBottom: '1px solid $colors$mauve5',
width: '100%',
minWidth: '240px',
flexDirection: 'column',
p: '$7',
backgroundColor: '$mauve2',
'@md': {
width: '30%',
maxWidth: '300px',
borderBottom: '0px',
borderRight: '1px solid $colors$mauve5'
}
}}
>
<DialogTitle
css={{
textTransform: "uppercase",
display: "inline-flex",
alignItems: "center",
gap: "$3",
fontSize: "$xl",
lineHeight: "$one",
fontWeight: "$bold",
textTransform: 'uppercase',
display: 'inline-flex',
alignItems: 'center',
gap: '$3',
fontSize: '$xl',
lineHeight: '$one',
fontWeight: '$bold'
}}
>
<Logo width="48px" height="48px" /> XRPL Hooks Builder
@@ -196,30 +185,27 @@ const Navigation = () => {
<DialogDescription as="div">
<Text
css={{
display: "inline-flex",
color: "inherit",
my: "$5",
mb: "$7",
display: 'inline-flex',
color: 'inherit',
my: '$5',
mb: '$7'
}}
>
Hooks add smart contract functionality to the XRP
Ledger.
Hooks add smart contract functionality to the XRP Ledger.
</Text>
<Flex
css={{ flexDirection: "column", gap: "$2", mt: "$2" }}
>
<Flex css={{ flexDirection: 'column', gap: '$2', mt: '$2' }}>
<Text
css={{
display: "inline-flex",
alignItems: "center",
gap: "$3",
color: "$purple11",
"&:hover": {
color: "$purple12",
},
"&:focus": {
outline: 0,
display: 'inline-flex',
alignItems: 'center',
gap: '$3',
color: '$purple11',
'&:hover': {
color: '$purple12'
},
'&:focus': {
outline: 0
}
}}
as="a"
rel="noreferrer noopener"
@@ -231,16 +217,16 @@ const Navigation = () => {
<Text
css={{
display: "inline-flex",
alignItems: "center",
gap: "$3",
color: "$purple11",
"&:hover": {
color: "$purple12",
},
"&:focus": {
outline: 0,
display: 'inline-flex',
alignItems: 'center',
gap: '$3',
color: '$purple11',
'&:hover': {
color: '$purple12'
},
'&:focus': {
outline: 0
}
}}
as="a"
rel="noreferrer noopener"
@@ -251,16 +237,16 @@ const Navigation = () => {
</Text>
<Text
css={{
display: "inline-flex",
alignItems: "center",
gap: "$3",
color: "$purple11",
"&:hover": {
color: "$purple12",
},
"&:focus": {
outline: 0,
display: 'inline-flex',
alignItems: 'center',
gap: '$3',
color: '$purple11',
'&:hover': {
color: '$purple12'
},
'&:focus': {
outline: 0
}
}}
as="a"
rel="noreferrer noopener"
@@ -275,32 +261,28 @@ const Navigation = () => {
<Flex
css={{
display: "grid",
gridTemplateColumns: "1fr",
gridTemplateRows: "max-content",
display: 'grid',
gridTemplateColumns: '1fr',
gridTemplateRows: 'max-content',
flex: 1,
p: "$7",
pb: "$16",
gap: "$3",
alignItems: "normal",
flexWrap: "wrap",
backgroundColor: "$mauve1",
"@md": {
gridTemplateColumns: "1fr 1fr",
gridTemplateRows: "max-content",
},
"@lg": {
gridTemplateColumns: "1fr 1fr 1fr",
gridTemplateRows: "max-content",
p: '$7',
pb: '$16',
gap: '$3',
alignItems: 'normal',
flexWrap: 'wrap',
backgroundColor: '$mauve1',
'@md': {
gridTemplateColumns: '1fr 1fr',
gridTemplateRows: 'max-content'
},
'@lg': {
gridTemplateColumns: '1fr 1fr 1fr',
gridTemplateRows: 'max-content'
}
}}
>
{Object.values(templateFileIds).map((template) => (
<PanelBox
key={template.id}
as="a"
href={`/develop/${template.id}`}
>
{Object.values(templateFileIds).map(template => (
<PanelBox key={template.id} as="a" href={`/develop/${template.id}`}>
<ImageWrapper>{template.icon()}</ImageWrapper>
<Heading>{template.name}</Heading>
@@ -312,14 +294,14 @@ const Navigation = () => {
<DialogClose asChild>
<Box
css={{
position: "absolute",
top: "$1",
right: "$1",
cursor: "pointer",
background: "$mauve1",
display: "flex",
borderRadius: "$full",
p: "$1",
position: 'absolute',
top: '$1',
right: '$1',
cursor: 'pointer',
background: '$mauve1',
display: 'flex',
borderRadius: '$full',
p: '$1'
}}
>
<X size="20px" />
@@ -333,63 +315,39 @@ const Navigation = () => {
</Flex>
<Flex
css={{
flexWrap: "nowrap",
marginLeft: "$4",
overflowX: "scroll",
"&::-webkit-scrollbar": {
flexWrap: 'nowrap',
marginLeft: '$4',
overflowX: 'scroll',
'&::-webkit-scrollbar': {
height: 0,
background: "transparent",
background: 'transparent'
},
scrollbarColor: "transparent",
scrollbarWidth: "none",
scrollbarColor: 'transparent',
scrollbarWidth: 'none'
}}
>
<Stack
css={{
ml: "$4",
gap: "$3",
flexWrap: "nowrap",
alignItems: "center",
marginLeft: "auto",
ml: '$4',
gap: '$3',
flexWrap: 'nowrap',
alignItems: 'center',
marginLeft: 'auto'
}}
>
<ButtonGroup>
<Link
href={gistId ? `/develop/${gistId}` : "/develop"}
passHref
shallow
>
<Button
as="a"
outline={!router.pathname.includes("/develop")}
uppercase
>
<Link href={gistId ? `/develop/${gistId}` : '/develop'} passHref shallow>
<Button as="a" outline={!router.pathname.includes('/develop')} uppercase>
Develop
</Button>
</Link>
<Link
href={gistId ? `/deploy/${gistId}` : "/deploy"}
passHref
shallow
>
<Button
as="a"
outline={!router.pathname.includes("/deploy")}
uppercase
>
<Link href={gistId ? `/deploy/${gistId}` : '/deploy'} passHref shallow>
<Button as="a" outline={!router.pathname.includes('/deploy')} uppercase>
Deploy
</Button>
</Link>
<Link
href={gistId ? `/test/${gistId}` : "/test"}
passHref
shallow
>
<Button
as="a"
outline={!router.pathname.includes("/test")}
uppercase
>
<Link href={gistId ? `/test/${gistId}` : '/test'} passHref shallow>
<Button as="a" outline={!router.pathname.includes('/test')} uppercase>
Test
</Button>
</Link>
@@ -405,7 +363,7 @@ const Navigation = () => {
</Flex>
</Container>
</Box>
);
};
)
}
export default Navigation;
export default Navigation

View File

@@ -1,30 +1,30 @@
import { styled } from "../stitches.config";
import Heading from "./Heading";
import Text from "./Text";
import { styled } from '../stitches.config'
import Heading from './Heading'
import Text from './Text'
const PanelBox = styled("div", {
display: "flex",
flexDirection: "column",
border: "1px solid $colors$mauve6",
backgroundColor: "$mauve2",
padding: "$3",
borderRadius: "$sm",
fontWeight: "lighter",
height: "auto",
cursor: "pointer",
flex: "1 1 0px",
"&:hover": {
border: "1px solid $colors$mauve9",
const PanelBox = styled('div', {
display: 'flex',
flexDirection: 'column',
border: '1px solid $colors$mauve6',
backgroundColor: '$mauve2',
padding: '$3',
borderRadius: '$sm',
fontWeight: 'lighter',
height: 'auto',
cursor: 'pointer',
flex: '1 1 0px',
'&:hover': {
border: '1px solid $colors$mauve9'
},
[`& ${Heading}`]: {
fontWeight: "lighter",
mb: "$2",
fontWeight: 'lighter',
mb: '$2'
},
[`& ${Text}`]: {
fontWeight: "lighter",
color: "$mauve10",
fontSize: "$sm",
},
});
fontWeight: 'lighter',
color: '$mauve10',
fontSize: '$sm'
}
})
export default PanelBox;
export default PanelBox

View File

@@ -1,92 +1,89 @@
import React, { ReactNode } from "react";
import * as PopoverPrimitive from "@radix-ui/react-popover";
import { styled, keyframes } from "../stitches.config";
import React, { ReactNode } from 'react'
import * as PopoverPrimitive from '@radix-ui/react-popover'
import { styled, keyframes } from '../stitches.config'
const slideUpAndFade = keyframes({
"0%": { opacity: 0, transform: "translateY(2px)" },
"100%": { opacity: 1, transform: "translateY(0)" },
});
'0%': { opacity: 0, transform: 'translateY(2px)' },
'100%': { opacity: 1, transform: 'translateY(0)' }
})
const slideRightAndFade = keyframes({
"0%": { opacity: 0, transform: "translateX(-2px)" },
"100%": { opacity: 1, transform: "translateX(0)" },
});
'0%': { opacity: 0, transform: 'translateX(-2px)' },
'100%': { opacity: 1, transform: 'translateX(0)' }
})
const slideDownAndFade = keyframes({
"0%": { opacity: 0, transform: "translateY(-2px)" },
"100%": { opacity: 1, transform: "translateY(0)" },
});
'0%': { opacity: 0, transform: 'translateY(-2px)' },
'100%': { opacity: 1, transform: 'translateY(0)' }
})
const slideLeftAndFade = keyframes({
"0%": { opacity: 0, transform: "translateX(2px)" },
"100%": { opacity: 1, transform: "translateX(0)" },
});
'0%': { opacity: 0, transform: 'translateX(2px)' },
'100%': { opacity: 1, transform: 'translateX(0)' }
})
const StyledContent = styled(PopoverPrimitive.Content, {
borderRadius: 4,
padding: "$3 $3",
padding: '$3 $3',
fontSize: 12,
lineHeight: 1,
color: "$text",
backgroundColor: "$background",
color: '$text',
backgroundColor: '$background',
boxShadow:
"0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2)",
"@media (prefers-reduced-motion: no-preference)": {
animationDuration: "400ms",
animationTimingFunction: "cubic-bezier(0.16, 1, 0.3, 1)",
willChange: "transform, opacity",
'0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2)',
'@media (prefers-reduced-motion: no-preference)': {
animationDuration: '400ms',
animationTimingFunction: 'cubic-bezier(0.16, 1, 0.3, 1)',
willChange: 'transform, opacity',
'&[data-state="open"]': {
'&[data-side="top"]': { animationName: slideDownAndFade },
'&[data-side="right"]': { animationName: slideLeftAndFade },
'&[data-side="bottom"]': { animationName: slideUpAndFade },
'&[data-side="left"]': { animationName: slideRightAndFade },
'&[data-side="left"]': { animationName: slideRightAndFade }
}
},
},
".dark &": {
backgroundColor: "$mauve5",
boxShadow:
"0px 5px 38px -2px rgba(22, 23, 24, 1), 0px 10px 20px 0px rgba(22, 23, 24, 1)",
},
});
'.dark &': {
backgroundColor: '$mauve5',
boxShadow: '0px 5px 38px -2px rgba(22, 23, 24, 1), 0px 10px 20px 0px rgba(22, 23, 24, 1)'
}
})
const StyledArrow = styled(PopoverPrimitive.Arrow, {
fill: "$colors$mauve2",
".dark &": {
fill: "$mauve5",
},
});
fill: '$colors$mauve2',
'.dark &': {
fill: '$mauve5'
}
})
const StyledClose = styled(PopoverPrimitive.Close, {
all: "unset",
fontFamily: "inherit",
borderRadius: "100%",
all: 'unset',
fontFamily: 'inherit',
borderRadius: '100%',
height: 25,
width: 25,
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
color: "$text",
position: "absolute",
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
color: '$text',
position: 'absolute',
top: 5,
right: 5,
});
right: 5
})
// Exports
export const PopoverRoot = PopoverPrimitive.Root;
export const PopoverTrigger = PopoverPrimitive.Trigger;
export const PopoverContent = StyledContent;
export const PopoverArrow = StyledArrow;
export const PopoverClose = StyledClose;
export const PopoverRoot = PopoverPrimitive.Root
export const PopoverTrigger = PopoverPrimitive.Trigger
export const PopoverContent = StyledContent
export const PopoverArrow = StyledArrow
export const PopoverClose = StyledClose
interface IPopover {
content: string | ReactNode;
open?: boolean;
defaultOpen?: boolean;
onOpenChange?: (open: boolean) => void;
content: string | ReactNode
open?: boolean
defaultOpen?: boolean
onOpenChange?: (open: boolean) => void
}
const Popover: React.FC<
IPopover & React.ComponentProps<typeof PopoverContent>
> = ({
const Popover: React.FC<IPopover & React.ComponentProps<typeof PopoverContent>> = ({
children,
content,
open,
@@ -94,16 +91,12 @@ const Popover: React.FC<
onOpenChange,
...rest
}) => (
<PopoverRoot
open={open}
defaultOpen={defaultOpen}
onOpenChange={onOpenChange}
>
<PopoverRoot open={open} defaultOpen={defaultOpen} onOpenChange={onOpenChange}>
<PopoverTrigger asChild>{children}</PopoverTrigger>
<PopoverContent sideOffset={5} {...rest}>
{content} <PopoverArrow offset={5} className="arrow" />
</PopoverContent>
</PopoverRoot>
);
)
export default Popover;
export default Popover

View File

@@ -1,15 +1,15 @@
import { styled } from "../stitches.config";
import { styled } from '../stitches.config'
const Pre = styled("span", {
const Pre = styled('span', {
m: 0,
wordBreak: "break-all",
wordBreak: 'break-all',
fontFamily: '$monospace',
whiteSpace: 'pre-wrap',
variants: {
fluid: {
true: {
width: "100%",
},
width: '100%'
}
},
line: {
true: {
@@ -21,7 +21,7 @@ const Pre = styled("span", {
display: 'block'
}
}
},
});
}
})
export default Pre;
export default Pre

View File

@@ -1,40 +1,35 @@
import { Play, X } from "phosphor-react";
import {
HTMLInputTypeAttribute,
useCallback,
useEffect,
useState,
} from "react";
import state, { IAccount, IFile, ILog } from "../../state";
import Button from "../Button";
import Box from "../Box";
import Input, { Label } from "../Input";
import Stack from "../Stack";
import { Play, X } from 'phosphor-react'
import { HTMLInputTypeAttribute, useCallback, useEffect, useState } from 'react'
import state, { IAccount, IFile, ILog } from '../../state'
import Button from '../Button'
import Box from '../Box'
import Input, { Label } from '../Input'
import Stack from '../Stack'
import {
Dialog,
DialogTrigger,
DialogContent,
DialogTitle,
DialogDescription,
DialogClose,
} from "../Dialog";
import Flex from "../Flex";
import { useSnapshot } from "valtio";
import Select from "../Select";
import Text from "../Text";
import { saveFile } from "../../state/actions/saveFile";
import { getErrors, getTags } from "../../utils/comment-parser";
import toast from "react-hot-toast";
DialogClose
} from '../Dialog'
import Flex from '../Flex'
import { useSnapshot } from 'valtio'
import Select from '../Select'
import Text from '../Text'
import { saveFile } from '../../state/actions/saveFile'
import { getErrors, getTags } from '../../utils/comment-parser'
import toast from 'react-hot-toast'
const generateHtmlTemplate = (code: string, data?: Record<string, any>) => {
let processString: string | undefined;
const process = { env: { NODE_ENV: "production" } } as any;
let processString: string | undefined
const process = { env: { NODE_ENV: 'production' } } as any
if (data) {
Object.keys(data).forEach(key => {
process.env[key] = data[key];
});
process.env[key] = data[key]
})
}
processString = JSON.stringify(process);
processString = JSON.stringify(process)
return `
<html>
@@ -66,7 +61,7 @@ const generateHtmlTemplate = (code: string, data?: Record<string, any>) => {
}
var process = '${processString || "{}"}';
var process = '${processString || '{}'}';
process = JSON.parse(process);
window.process = process
@@ -85,112 +80,107 @@ const generateHtmlTemplate = (code: string, data?: Record<string, any>) => {
<body>
</body>
</html>
`;
};
`
}
type Fields = Record<
string,
{
name: string;
value: string;
type?: "Account" | `Account.${keyof IAccount}` | HTMLInputTypeAttribute;
description?: string;
required?: boolean;
name: string
value: string
type?: 'Account' | `Account.${keyof IAccount}` | HTMLInputTypeAttribute
description?: string
required?: boolean
}
>;
>
const RunScript: React.FC<{ file: IFile }> = ({ file: { content, name } }) => {
const snap = useSnapshot(state);
const [templateError, setTemplateError] = useState("");
const [fields, setFields] = useState<Fields>({});
const [iFrameCode, setIframeCode] = useState("");
const [isDialogOpen, setIsDialogOpen] = useState(false);
const snap = useSnapshot(state)
const [templateError, setTemplateError] = useState('')
const [fields, setFields] = useState<Fields>({})
const [iFrameCode, setIframeCode] = useState('')
const [isDialogOpen, setIsDialogOpen] = useState(false)
const getFields = useCallback(() => {
const inputTags = ["input", "param", "arg", "argument"];
const inputTags = ['input', 'param', 'arg', 'argument']
const tags = getTags(content)
.filter(tag => inputTags.includes(tag.tag))
.filter(tag => !!tag.name);
.filter(tag => !!tag.name)
let _fields = tags.map(tag => ({
name: tag.name,
value: tag.default || "",
value: tag.default || '',
type: tag.type,
description: tag.description,
required: !tag.optional,
}));
required: !tag.optional
}))
const fields: Fields = _fields.reduce((acc, field) => {
acc[field.name] = field;
return acc;
}, {} as Fields);
acc[field.name] = field
return acc
}, {} as Fields)
const error = getErrors(content);
if (error) setTemplateError(error.message);
else setTemplateError("");
const error = getErrors(content)
if (error) setTemplateError(error.message)
else setTemplateError('')
return fields;
}, [content]);
return fields
}, [content])
const runScript = useCallback(() => {
try {
let data: any = {};
let data: any = {}
Object.keys(fields).forEach(key => {
data[key] = fields[key].value;
});
const template = generateHtmlTemplate(content, data);
data[key] = fields[key].value
})
const template = generateHtmlTemplate(content, data)
setIframeCode(template);
setIframeCode(template)
state.scriptLogs = [
{ type: "success", message: "Started running..." },
];
state.scriptLogs = [{ type: 'success', message: 'Started running...' }]
} catch (err) {
state.scriptLogs = [
...snap.scriptLogs,
// @ts-expect-error
{ type: "error", message: err?.message || "Could not parse template" },
];
{ type: 'error', message: err?.message || 'Could not parse template' }
]
}
}, [content, fields, snap.scriptLogs]);
}, [content, fields, snap.scriptLogs])
useEffect(() => {
const handleEvent = (e: any) => {
if (e.data.type === "log" || e.data.type === "error") {
if (e.data.type === 'log' || e.data.type === 'error') {
const data: ILog[] = e.data.args.map((msg: any) => ({
type: e.data.type,
message: typeof msg === "string" ? msg : JSON.stringify(msg, null, 2),
}));
state.scriptLogs = [...snap.scriptLogs, ...data];
message: typeof msg === 'string' ? msg : JSON.stringify(msg, null, 2)
}))
state.scriptLogs = [...snap.scriptLogs, ...data]
}
};
window.addEventListener("message", handleEvent);
return () => window.removeEventListener("message", handleEvent);
}, [snap.scriptLogs]);
}
window.addEventListener('message', handleEvent)
return () => window.removeEventListener('message', handleEvent)
}, [snap.scriptLogs])
useEffect(() => {
const defaultFields = getFields() || {};
setFields(defaultFields);
}, [content, setFields, getFields]);
const defaultFields = getFields() || {}
setFields(defaultFields)
}, [content, setFields, getFields])
const accOptions = snap.accounts?.map(acc => ({
...acc,
label: acc.name,
value: acc.address,
}));
value: acc.address
}))
const isDisabled = Object.values(fields).some(
field => field.required && !field.value
);
const isDisabled = Object.values(fields).some(field => field.required && !field.value)
const handleRun = useCallback(() => {
if (isDisabled)
return toast.error("Please fill in all the required fields.");
if (isDisabled) return toast.error('Please fill in all the required fields.')
state.scriptLogs = [];
runScript();
setIsDialogOpen(false);
}, [isDisabled, runScript]);
state.scriptLogs = []
runScript()
setIsDialogOpen(false)
}, [isDisabled, runScript])
return (
<>
@@ -199,8 +189,8 @@ const RunScript: React.FC<{ file: IFile }> = ({ file: { content, name } }) => {
<Button
variant="primary"
onClick={() => {
saveFile(false);
setIframeCode("");
saveFile(false)
setIframeCode('')
}}
>
<Play weight="bold" size="16px" /> {name}
@@ -210,97 +200,86 @@ const RunScript: React.FC<{ file: IFile }> = ({ file: { content, name } }) => {
<DialogTitle>Run {name} script</DialogTitle>
<DialogDescription>
<Box>
You are about to run scripts provided by the developer of the
hook, make sure you trust the author before you continue.
You are about to run scripts provided by the developer of the hook, make sure you
trust the author before you continue.
</Box>
{templateError && (
<Box
as="span"
css={{
display: "block",
color: "$error",
mt: "$3",
whiteSpace: "pre",
display: 'block',
color: '$error',
mt: '$3',
whiteSpace: 'pre'
}}
>
{templateError}
</Box>
)}
{Object.keys(fields).length > 0 && (
<Box css={{ mt: "$4", mb: 0 }}>
<Box css={{ mt: '$4', mb: 0 }}>
Fill in the following parameters to run the script.
</Box>
)}
</DialogDescription>
<Stack css={{ width: "100%" }}>
<Stack css={{ width: '100%' }}>
{Object.keys(fields).map(key => {
const { name, value, type, description, required } = fields[key];
const { name, value, type, description, required } = fields[key]
const isAccount = type?.startsWith("Account");
const isAccountSecret = type === "Account.secret";
const isAccount = type?.startsWith('Account')
const isAccountSecret = type === 'Account.secret'
const accountField =
(isAccount && type?.split(".")[1]) || "address";
const accountField = (isAccount && type?.split('.')[1]) || 'address'
return (
<Box key={name} css={{ width: "100%" }}>
<Label
css={{ display: "flex", justifyContent: "space-between" }}
>
<Box key={name} css={{ width: '100%' }}>
<Label css={{ display: 'flex', justifyContent: 'space-between' }}>
<span>
{description || name} {required && <Text error>*</Text>}
</span>
{isAccountSecret && (
<Text error small css={{ alignSelf: "end" }}>
<Text error small css={{ alignSelf: 'end' }}>
can access account secret key
</Text>
)}
</Label>
{isAccount ? (
<Select
css={{ mt: "$1" }}
css={{ mt: '$1' }}
options={accOptions}
onChange={(val: any) => {
setFields({
...fields,
[key]: {
...fields[key],
value: val[accountField],
},
});
value: val[accountField]
}
})
}}
value={accOptions.find(
(acc: any) => acc[accountField] === value
)}
value={accOptions.find((acc: any) => acc[accountField] === value)}
/>
) : (
<Input
type={type || "text"}
type={type || 'text'}
value={value}
css={{ mt: "$1" }}
css={{ mt: '$1' }}
onChange={e => {
setFields({
...fields,
[key]: { ...fields[key], value: e.target.value },
});
[key]: { ...fields[key], value: e.target.value }
})
}}
/>
)}
</Box>
);
)
})}
<Flex
css={{ justifyContent: "flex-end", width: "100%", gap: "$3" }}
>
<Flex css={{ justifyContent: 'flex-end', width: '100%', gap: '$3' }}>
<DialogClose asChild>
<Button outline>Cancel</Button>
</DialogClose>
<Button
variant="primary"
isDisabled={isDisabled}
onClick={handleRun}
>
<Button variant="primary" isDisabled={isDisabled} onClick={handleRun}>
Run script
</Button>
</Flex>
@@ -308,14 +287,14 @@ const RunScript: React.FC<{ file: IFile }> = ({ file: { content, name } }) => {
<DialogClose asChild>
<Box
css={{
position: "absolute",
top: "$1",
right: "$1",
cursor: "pointer",
background: "$mauve1",
display: "flex",
borderRadius: "$full",
p: "$1",
position: 'absolute',
top: '$1',
right: '$1',
cursor: 'pointer',
background: '$mauve1',
display: 'flex',
borderRadius: '$full',
p: '$1'
}}
>
<X size="20px" />
@@ -324,14 +303,10 @@ const RunScript: React.FC<{ file: IFile }> = ({ file: { content, name } }) => {
</DialogContent>
</Dialog>
{iFrameCode && (
<iframe
style={{ display: "none" }}
srcDoc={iFrameCode}
sandbox="allow-scripts"
/>
<iframe style={{ display: 'none' }} srcDoc={iFrameCode} sandbox="allow-scripts" />
)}
</>
);
};
)
}
export default RunScript;
export default RunScript

View File

@@ -1,15 +1,15 @@
import { forwardRef } from "react";
import { mauve, mauveDark, purple, purpleDark } from "@radix-ui/colors";
import { useTheme } from "next-themes";
import { styled } from "../stitches.config";
import dynamic from "next/dynamic";
import type { Props } from "react-select";
const SelectInput = dynamic(() => import("react-select"), { ssr: false });
import { forwardRef } from 'react'
import { mauve, mauveDark, purple, purpleDark } from '@radix-ui/colors'
import { useTheme } from 'next-themes'
import { styled } from '../stitches.config'
import dynamic from 'next/dynamic'
import type { Props } from 'react-select'
const SelectInput = dynamic(() => import('react-select'), { ssr: false })
// eslint-disable-next-line react/display-name
const Select = forwardRef<any, Props>((props, ref) => {
const { theme } = useTheme();
const isDark = theme === "dark";
const { theme } = useTheme()
const isDark = theme === 'dark'
const colors: any = {
// primary: pink.pink9,
active: isDark ? purpleDark.purple9 : purple.purple9,
@@ -26,102 +26,97 @@ const Select = forwardRef<any, Props>((props, ref) => {
mauve9: isDark ? mauveDark.mauve9 : mauve.mauve9,
mauve12: isDark ? mauveDark.mauve12 : mauve.mauve12,
border: isDark ? mauveDark.mauve10 : mauve.mauve10,
placeholder: isDark ? mauveDark.mauve11 : mauve.mauve11,
};
colors.outline = colors.background;
colors.selected = colors.secondary;
placeholder: isDark ? mauveDark.mauve11 : mauve.mauve11
}
colors.outline = colors.background
colors.selected = colors.secondary
return (
<SelectInput
ref={ref}
menuPosition={props.menuPosition || "fixed"}
menuPosition={props.menuPosition || 'fixed'}
styles={{
container: (provided) => {
container: provided => {
return {
...provided,
position: "relative",
};
position: 'relative'
}
},
singleValue: (provided) => ({
singleValue: provided => ({
...provided,
color: colors.mauve12,
color: colors.mauve12
}),
menu: (provided) => ({
menu: provided => ({
...provided,
backgroundColor: colors.dropDownBg,
backgroundColor: colors.dropDownBg
}),
control: (provided, state) => {
return {
...provided,
minHeight: 0,
border: "0px",
border: '0px',
backgroundColor: colors.mauve4,
boxShadow: `0 0 0 1px ${
state.isFocused ? colors.border : colors.secondary
}`,
};
boxShadow: `0 0 0 1px ${state.isFocused ? colors.border : colors.secondary}`
}
},
input: (provided) => {
input: provided => {
return {
...provided,
color: "$text",
};
color: '$text'
}
},
multiValue: (provided) => {
multiValue: provided => {
return {
...provided,
backgroundColor: colors.mauve8,
};
backgroundColor: colors.mauve8
}
},
multiValueLabel: (provided) => {
multiValueLabel: provided => {
return {
...provided,
color: colors.mauve12,
};
color: colors.mauve12
}
},
multiValueRemove: (provided) => {
multiValueRemove: provided => {
return {
...provided,
":hover": {
background: colors.mauve9,
},
};
':hover': {
background: colors.mauve9
}
}
},
option: (provided, state) => {
return {
...provided,
color: colors.searchText,
backgroundColor:
state.isFocused
? colors.activeLight
: colors.dropDownBg,
":hover": {
backgroundColor: state.isFocused ? colors.activeLight : colors.dropDownBg,
':hover': {
backgroundColor: colors.active,
color: "#ffffff",
color: '#ffffff'
},
":selected": {
backgroundColor: "red",
':selected': {
backgroundColor: 'red'
}
}
},
};
},
indicatorSeparator: (provided) => {
indicatorSeparator: provided => {
return {
...provided,
backgroundColor: colors.secondary,
};
backgroundColor: colors.secondary
}
},
dropdownIndicator: (provided, state) => {
return {
...provided,
color: state.isFocused ? colors.border : colors.secondary,
":hover": {
color: colors.border,
},
};
},
':hover': {
color: colors.border
}
}
}
}}
{...props}
/>
);
});
)
})
export default styled(Select, {});
export default styled(Select, {})

View File

@@ -1,77 +1,65 @@
import React, { useCallback, useEffect, useState } from "react";
import { Plus, Trash, X } from "phosphor-react";
import { Button, Box, Text } from ".";
import { Stack, Flex, Select } from ".";
import React, { useCallback, useEffect, useState } from 'react'
import { Plus, Trash, X } from 'phosphor-react'
import { Button, Box, Text } from '.'
import { Stack, Flex, Select } from '.'
import {
Dialog,
DialogContent,
DialogTitle,
DialogDescription,
DialogClose,
DialogTrigger,
} from "./Dialog";
import { Input, Label } from "./Input";
import {
Controller,
SubmitHandler,
useFieldArray,
useForm,
} from "react-hook-form";
DialogTrigger
} from './Dialog'
import { Input, Label } from './Input'
import { Controller, SubmitHandler, useFieldArray, useForm } from 'react-hook-form'
import { deployHook } from "../state/actions";
import { useSnapshot } from "valtio";
import state, { IFile, SelectOption } from "../state";
import toast from "react-hot-toast";
import { prepareDeployHookTx, sha256 } from "../state/actions/deployHook";
import estimateFee from "../utils/estimateFee";
import {
getParameters,
getInvokeOptions,
transactionOptions,
SetHookData,
} from "../utils/setHook";
import { capitalize } from "../utils/helpers";
import { deployHook } from '../state/actions'
import { useSnapshot } from 'valtio'
import state, { IFile, SelectOption } from '../state'
import toast from 'react-hot-toast'
import { prepareDeployHookTx, sha256 } from '../state/actions/deployHook'
import estimateFee from '../utils/estimateFee'
import { getParameters, getInvokeOptions, transactionOptions, SetHookData } from '../utils/setHook'
import { capitalize } from '../utils/helpers'
export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
({ accountAddress }) => {
const snap = useSnapshot(state);
const snap = useSnapshot(state)
const [estimateLoading, setEstimateLoading] = useState(false);
const [isSetHookDialogOpen, setIsSetHookDialogOpen] = useState(false);
const [estimateLoading, setEstimateLoading] = useState(false)
const [isSetHookDialogOpen, setIsSetHookDialogOpen] = useState(false)
const compiledFiles = snap.files.filter(file => file.compiledContent);
const activeFile = compiledFiles[snap.activeWat] as IFile | undefined;
const compiledFiles = snap.files.filter(file => file.compiledContent)
const activeFile = compiledFiles[snap.activeWat] as IFile | undefined
const accountOptions: SelectOption[] = snap.accounts.map(acc => ({
label: acc.name,
value: acc.address,
}));
value: acc.address
}))
const [selectedAccount, setSelectedAccount] = useState(
accountOptions.find(acc => acc.value === accountAddress)
);
const account = snap.accounts.find(
acc => acc.address === selectedAccount?.value
);
)
const account = snap.accounts.find(acc => acc.address === selectedAccount?.value)
const getHookNamespace = useCallback(
() =>
(activeFile && snap.deployValues[activeFile.name]?.HookNamespace) ||
activeFile?.name.split(".")[0] ||
"",
activeFile?.name.split('.')[0] ||
'',
[activeFile, snap.deployValues]
);
)
const getDefaultValues = useCallback((): Partial<SetHookData> => {
const content = activeFile?.compiledValueSnapshot;
const content = activeFile?.compiledValueSnapshot
return (
(activeFile && snap.deployValues[activeFile.name]) || {
HookNamespace: getHookNamespace(),
Invoke: getInvokeOptions(content),
HookParameters: getParameters(content),
HookParameters: getParameters(content)
}
);
}, [activeFile, getHookNamespace, snap.deployValues]);
)
}, [activeFile, getHookNamespace, snap.deployValues])
const {
register,
@@ -81,33 +69,30 @@ export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
setValue,
getValues,
reset,
formState: { errors },
formState: { errors }
} = useForm<SetHookData>({
defaultValues: getDefaultValues(),
});
defaultValues: getDefaultValues()
})
const { fields, append, remove } = useFieldArray({
control,
name: "HookParameters", // unique name for your Field Array
});
name: 'HookParameters' // unique name for your Field Array
})
const watchedFee = watch("Fee");
const watchedFee = watch('Fee')
// Reset form if activeFile changes
useEffect(() => {
if (!activeFile) return;
const defaultValues = getDefaultValues();
if (!activeFile) return
const defaultValues = getDefaultValues()
reset(defaultValues);
}, [activeFile, getDefaultValues, reset]);
reset(defaultValues)
}, [activeFile, getDefaultValues, reset])
useEffect(() => {
if (
watchedFee &&
(watchedFee.includes(".") || watchedFee.includes(","))
) {
setValue("Fee", watchedFee.replaceAll(".", "").replaceAll(",", ""));
if (watchedFee && (watchedFee.includes('.') || watchedFee.includes(','))) {
setValue('Fee', watchedFee.replaceAll('.', '').replaceAll(',', ''))
}
}, [watchedFee, setValue]);
}, [watchedFee, setValue])
// const {
// fields: grantFields,
// append: grantAppend,
@@ -116,67 +101,67 @@ export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
// control,
// name: "HookGrants", // unique name for your Field Array
// });
const [hashedNamespace, setHashedNamespace] = useState("");
const [hashedNamespace, setHashedNamespace] = useState('')
const namespace = watch("HookNamespace", getHookNamespace());
const namespace = watch('HookNamespace', getHookNamespace())
const calculateHashedValue = useCallback(async () => {
const hashedVal = await sha256(namespace);
setHashedNamespace(hashedVal.toUpperCase());
}, [namespace]);
const hashedVal = await sha256(namespace)
setHashedNamespace(hashedVal.toUpperCase())
}, [namespace])
useEffect(() => {
calculateHashedValue();
}, [namespace, calculateHashedValue]);
calculateHashedValue()
}, [namespace, calculateHashedValue])
const calculateFee = useCallback(async () => {
if (!account) return;
if (!account) return
const formValues = getValues();
const tx = await prepareDeployHookTx(account, formValues);
const formValues = getValues()
const tx = await prepareDeployHookTx(account, formValues)
if (!tx) {
return;
return
}
const res = await estimateFee(tx, account);
const res = await estimateFee(tx, account)
if (res && res.base_fee) {
setValue("Fee", Math.round(Number(res.base_fee || "")).toString());
setValue('Fee', Math.round(Number(res.base_fee || '')).toString())
}
}, [account, getValues, setValue]);
}, [account, getValues, setValue])
const tooLargeFile = () => {
return Boolean(
activeFile?.compiledContent?.byteLength &&
activeFile?.compiledContent?.byteLength >= 64000
);
};
activeFile?.compiledContent?.byteLength && activeFile?.compiledContent?.byteLength >= 64000
)
}
const onSubmit: SubmitHandler<SetHookData> = async data => {
const currAccount = state.accounts.find(
acc => acc.address === account?.address
);
if (!account) return;
if (currAccount) currAccount.isLoading = true;
const currAccount = state.accounts.find(acc => acc.address === account?.address)
if (!account) return
if (currAccount) currAccount.isLoading = true
data.HookParameters.forEach(param => {
delete param.$metaData;
return param;
});
delete param.$metaData
return param
})
const res = await deployHook(account, data);
if (currAccount) currAccount.isLoading = false;
const res = await deployHook(account, data)
if (currAccount) currAccount.isLoading = false
if (res && res.engine_result === "tesSUCCESS") {
toast.success("Transaction succeeded!");
return setIsSetHookDialogOpen(false);
if (res && res.engine_result === 'tesSUCCESS') {
toast.success('Transaction succeeded!')
return setIsSetHookDialogOpen(false)
}
toast.error(`Transaction failed! (${res?.engine_result_message})`)
}
toast.error(`Transaction failed! (${res?.engine_result_message})`);
};
const onOpenChange = useCallback((open: boolean) => {
setIsSetHookDialogOpen(open);
const onOpenChange = useCallback(
(open: boolean) => {
setIsSetHookDialogOpen(open)
if (open) calculateFee();
}, [calculateFee]);
if (open) calculateFee()
},
[calculateFee]
)
return (
<Dialog open={isSetHookDialogOpen} onOpenChange={onOpenChange}>
<DialogTrigger asChild>
@@ -184,10 +169,8 @@ export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
ghost
size="xs"
uppercase
variant={"secondary"}
disabled={
!account || account.isLoading || !activeFile || tooLargeFile()
}
variant={'secondary'}
disabled={!account || account.isLoading || !activeFile || tooLargeFile()}
>
Set Hook
</Button>
@@ -196,8 +179,8 @@ export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
<form onSubmit={handleSubmit(onSubmit)}>
<DialogTitle>Deploy configuration</DialogTitle>
<DialogDescription as="div">
<Stack css={{ width: "100%", flex: 1 }}>
<Box css={{ width: "100%" }}>
<Stack css={{ width: '100%', flex: 1 }}>
<Box css={{ width: '100%' }}>
<Label>Account</Label>
<Select
instanceId="deploy-account"
@@ -207,7 +190,7 @@ export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
onChange={(acc: any) => setSelectedAccount(acc)}
/>
</Box>
<Box css={{ width: "100%" }}>
<Box css={{ width: '100%' }}>
<Label>Invoke on transactions</Label>
<Controller
name="Invoke"
@@ -223,27 +206,20 @@ export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
)}
/>
</Box>
<Box css={{ width: "100%" }}>
<Box css={{ width: '100%' }}>
<Label>Hook Namespace Seed</Label>
<Input
{...register("HookNamespace", { required: true })}
autoComplete={"off"}
/>
{errors.HookNamespace?.type === "required" && (
<Box css={{ display: "inline", color: "$red11" }}>
Namespace is required
</Box>
<Input {...register('HookNamespace', { required: true })} autoComplete={'off'} />
{errors.HookNamespace?.type === 'required' && (
<Box css={{ display: 'inline', color: '$red11' }}>Namespace is required</Box>
)}
<Box css={{ mt: "$3" }}>
<Box css={{ mt: '$3' }}>
<Label>Hook Namespace (sha256)</Label>
<Input readOnly value={hashedNamespace} />
</Box>
</Box>
<Box css={{ width: "100%" }}>
<Label style={{ marginBottom: "10px", display: "block" }}>
Hook parameters
</Label>
<Box css={{ width: '100%' }}>
<Label style={{ marginBottom: '10px', display: 'block' }}>Hook parameters</Label>
<Stack>
{fields.map((field, index) => (
<Stack key={field.id}>
@@ -258,25 +234,20 @@ export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
)}
/>
<Input
css={{ mx: "$2" }}
css={{ mx: '$2' }}
placeholder="Value (hex-quoted)"
{...register(
`HookParameters.${index}.HookParameter.HookParameterValue`,
{ required: field.$metaData?.required }
)}
/>
<Button
onClick={() => remove(index)}
variant="destroy"
>
<Button onClick={() => remove(index)} variant="destroy">
<Trash weight="regular" size="16px" />
</Button>
</Flex>
{errors.HookParameters?.[index]?.HookParameter
?.HookParameterValue?.type === "required" && (
<Text error>This field is required</Text>
)}
<Label css={{ fontSize: "$sm", mt: "$1" }}>
{errors.HookParameters?.[index]?.HookParameter?.HookParameterValue
?.type === 'required' && <Text error>This field is required</Text>}
<Label css={{ fontSize: '$sm', mt: '$1' }}>
{capitalize(field.$metaData?.description)}
</Label>
</Flex>
@@ -289,9 +260,9 @@ export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
onClick={() =>
append({
HookParameter: {
HookParameterName: "",
HookParameterValue: "",
},
HookParameterName: '',
HookParameterValue: ''
}
})
}
>
@@ -300,30 +271,30 @@ export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
</Button>
</Stack>
</Box>
<Box css={{ width: "100%", position: "relative" }}>
<Box css={{ width: '100%', position: 'relative' }}>
<Label>Fee</Label>
<Box css={{ display: "flex", alignItems: "center" }}>
<Box css={{ display: 'flex', alignItems: 'center' }}>
<Input
type="number"
{...register("Fee", { required: true })}
autoComplete={"off"}
{...register('Fee', { required: true })}
autoComplete={'off'}
onKeyPress={e => {
if (e.key === "." || e.key === ",") {
e.preventDefault();
if (e.key === '.' || e.key === ',') {
e.preventDefault()
}
}}
step="1"
defaultValue={10000}
css={{
"-moz-appearance": "textfield",
"&::-webkit-outer-spin-button": {
"-webkit-appearance": "none",
margin: 0,
},
"&::-webkit-inner-spin-button ": {
"-webkit-appearance": "none",
margin: 0,
'-moz-appearance': 'textfield',
'&::-webkit-outer-spin-button': {
'-webkit-appearance': 'none',
margin: 0
},
'&::-webkit-inner-spin-button ': {
'-webkit-appearance': 'none',
margin: 0
}
}}
/>
<Button
@@ -332,47 +303,37 @@ export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
outline
isLoading={estimateLoading}
css={{
position: "absolute",
right: "$2",
fontSize: "$xs",
cursor: "pointer",
alignContent: "center",
display: "flex",
position: 'absolute',
right: '$2',
fontSize: '$xs',
cursor: 'pointer',
alignContent: 'center',
display: 'flex'
}}
onClick={async e => {
e.preventDefault();
if (!account) return;
setEstimateLoading(true);
const formValues = getValues();
e.preventDefault()
if (!account) return
setEstimateLoading(true)
const formValues = getValues()
try {
const tx = await prepareDeployHookTx(
account,
formValues
);
const tx = await prepareDeployHookTx(account, formValues)
if (tx) {
const res = await estimateFee(tx, account);
const res = await estimateFee(tx, account)
if (res && res.base_fee) {
setValue(
"Fee",
Math.round(
Number(res.base_fee || "")
).toString()
);
setValue('Fee', Math.round(Number(res.base_fee || '')).toString())
}
}
} catch (err) {}
setEstimateLoading(false);
setEstimateLoading(false)
}}
>
Suggest
</Button>
</Box>
{errors.Fee?.type === "required" && (
<Box css={{ display: "inline", color: "$red11" }}>
Fee is required
</Box>
{errors.Fee?.type === 'required' && (
<Box css={{ display: 'inline', color: '$red11' }}>Fee is required</Box>
)}
</Box>
{/* <Box css={{ width: "100%" }}>
@@ -429,35 +390,31 @@ export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
<Flex
css={{
marginTop: 25,
justifyContent: "flex-end",
gap: "$3",
justifyContent: 'flex-end',
gap: '$3'
}}
>
<DialogClose asChild>
<Button outline>Cancel</Button>
</DialogClose>
{/* <DialogClose asChild> */}
<Button
variant="primary"
type="submit"
isLoading={account?.isLoading}
>
<Button variant="primary" type="submit" isLoading={account?.isLoading}>
Set Hook
</Button>
{/* </DialogClose> */}
</Flex>
<DialogClose asChild>
<Box css={{ position: "absolute", top: "$3", right: "$3" }}>
<Box css={{ position: 'absolute', top: '$3', right: '$3' }}>
<X size="20px" />
</Box>
</DialogClose>
</form>
</DialogContent>
</Dialog>
);
)
}
);
)
SetHookDialog.displayName = "SetHookDialog";
SetHookDialog.displayName = 'SetHookDialog'
export default SetHookDialog;
export default SetHookDialog

View File

@@ -1,14 +1,14 @@
import { Spinner as SpinnerIcon } from "phosphor-react";
import { styled, keyframes } from "../stitches.config";
import { Spinner as SpinnerIcon } from 'phosphor-react'
import { styled, keyframes } from '../stitches.config'
const rotate = keyframes({
"0%": { transform: "rotate(0deg)" },
"100%": { transform: "rotate(-360deg)" },
});
'0%': { transform: 'rotate(0deg)' },
'100%': { transform: 'rotate(-360deg)' }
})
const Spinner = styled(SpinnerIcon, {
animation: `${rotate} 150ms linear infinite`,
fontSize: "16px",
});
fontSize: '16px'
})
export default Spinner;
export default Spinner

View File

@@ -1,11 +1,11 @@
import Box from "./Box";
import { styled } from "../stitches.config";
import Box from './Box'
import { styled } from '../stitches.config'
const StackComponent = styled(Box, {
display: "flex",
flexWrap: "wrap",
flexDirection: "row",
gap: "$4",
});
display: 'flex',
flexWrap: 'wrap',
flexDirection: 'row',
gap: '$4'
})
export default StackComponent;
export default StackComponent

View File

@@ -1,32 +1,32 @@
import { styled } from "../stitches.config";
import * as SwitchPrimitive from "@radix-ui/react-switch";
import { styled } from '../stitches.config'
import * as SwitchPrimitive from '@radix-ui/react-switch'
const StyledSwitch = styled(SwitchPrimitive.Root, {
all: "unset",
all: 'unset',
width: 42,
height: 25,
backgroundColor: "$mauve9",
borderRadius: "9999px",
position: "relative",
backgroundColor: '$mauve9',
borderRadius: '9999px',
position: 'relative',
boxShadow: `0 2px 10px $colors$mauve2`,
WebkitTapHighlightColor: "rgba(0, 0, 0, 0)",
"&:focus": { boxShadow: `0 0 0 2px $colors$mauveA2` },
'&[data-state="checked"]': { backgroundColor: "$green11" },
});
WebkitTapHighlightColor: 'rgba(0, 0, 0, 0)',
'&:focus': { boxShadow: `0 0 0 2px $colors$mauveA2` },
'&[data-state="checked"]': { backgroundColor: '$green11' }
})
const StyledThumb = styled(SwitchPrimitive.Thumb, {
display: "block",
display: 'block',
width: 21,
height: 21,
backgroundColor: "white",
borderRadius: "9999px",
backgroundColor: 'white',
borderRadius: '9999px',
boxShadow: `0 2px 2px $colors$mauveA6`,
transition: "transform 100ms",
transform: "translateX(2px)",
willChange: "transform",
'&[data-state="checked"]': { transform: "translateX(19px)" },
});
transition: 'transform 100ms',
transform: 'translateX(2px)',
willChange: 'transform',
'&[data-state="checked"]': { transform: 'translateX(19px)' }
})
// Exports
export const Switch = StyledSwitch;
export const SwitchThumb = StyledThumb;
export const Switch = StyledSwitch
export const SwitchThumb = StyledThumb

View File

@@ -1,64 +1,58 @@
import React, {
useEffect,
useState,
Fragment,
isValidElement,
useCallback,
} from "react";
import type { ReactNode, ReactElement } from "react";
import { Box, Button, Flex, Input, Label, Pre, Stack, Text } from ".";
import React, { useEffect, useState, Fragment, isValidElement, useCallback } from 'react'
import type { ReactNode, ReactElement } from 'react'
import { Box, Button, Flex, Input, Label, Pre, Stack, Text } from '.'
import {
Dialog,
DialogTrigger,
DialogContent,
DialogTitle,
DialogDescription,
DialogClose,
} from "./Dialog";
import { Plus, X } from "phosphor-react";
import { styled } from "../stitches.config";
import { capitalize } from "../utils/helpers";
import ContextMenu, { ContentMenuOption } from "./ContextMenu";
DialogClose
} from './Dialog'
import { Plus, X } from 'phosphor-react'
import { styled } from '../stitches.config'
import { capitalize } from '../utils/helpers'
import ContextMenu, { ContentMenuOption } from './ContextMenu'
const ErrorText = styled(Text, {
color: "$error",
mt: "$1",
display: "block",
});
color: '$error',
mt: '$1',
display: 'block'
})
type Nullable<T> = T | null | undefined | false;
type Nullable<T> = T | null | undefined | false
interface TabProps {
header: string;
children?: ReactNode;
header: string
children?: ReactNode
renameDisabled?: boolean
}
// TODO customize messages shown
interface Props {
label?: string;
activeIndex?: number;
activeHeader?: string;
headless?: boolean;
children: ReactElement<TabProps>[];
keepAllAlive?: boolean;
defaultExtension?: string;
extensionRequired?: boolean;
allowedExtensions?: string[];
label?: string
activeIndex?: number
activeHeader?: string
headless?: boolean
children: ReactElement<TabProps>[]
keepAllAlive?: boolean
defaultExtension?: string
extensionRequired?: boolean
allowedExtensions?: string[]
headerExtraValidation?: {
regex: string | RegExp;
error: string;
};
onCreateNewTab?: (name: string) => any;
onRenameTab?: (index: number, nwName: string, oldName?: string) => any;
onCloseTab?: (index: number, header?: string) => any;
onChangeActive?: (index: number, header?: string) => any;
regex: string | RegExp
error: string
}
onCreateNewTab?: (name: string) => any
onRenameTab?: (index: number, nwName: string, oldName?: string) => any
onCloseTab?: (index: number, header?: string) => any
onChangeActive?: (index: number, header?: string) => any
}
export const Tab = (props: TabProps) => null;
export const Tab = (props: TabProps) => null
export const Tabs = ({
label = "Tab",
label = 'Tab',
children,
activeIndex,
activeHeader,
@@ -70,157 +64,135 @@ export const Tabs = ({
onRenameTab,
headerExtraValidation,
extensionRequired,
defaultExtension = "",
allowedExtensions,
defaultExtension = '',
allowedExtensions
}: Props) => {
const [active, setActive] = useState(activeIndex || 0);
const tabs: TabProps[] = children.map(elem => elem.props);
const [active, setActive] = useState(activeIndex || 0)
const tabs: TabProps[] = children.map(elem => elem.props)
const [isNewtabDialogOpen, setIsNewtabDialogOpen] = useState(false);
const [renamingTab, setRenamingTab] = useState<number | null>(null);
const [tabname, setTabname] = useState("");
const [tabnameError, setTabnameError] = useState<string | null>(null);
const [isNewtabDialogOpen, setIsNewtabDialogOpen] = useState(false)
const [renamingTab, setRenamingTab] = useState<number | null>(null)
const [tabname, setTabname] = useState('')
const [tabnameError, setTabnameError] = useState<string | null>(null)
useEffect(() => {
if (activeIndex) setActive(activeIndex);
}, [activeIndex]);
if (activeIndex) setActive(activeIndex)
}, [activeIndex])
useEffect(() => {
if (activeHeader) {
const idx = tabs.findIndex(tab => tab.header === activeHeader);
if (idx !== -1) setActive(idx);
else setActive(0);
const idx = tabs.findIndex(tab => tab.header === activeHeader)
if (idx !== -1) setActive(idx)
else setActive(0)
}
}, [activeHeader, tabs]);
}, [activeHeader, tabs])
// when filename changes, reset error
useEffect(() => {
setTabnameError(null);
}, [tabname, setTabnameError]);
setTabnameError(null)
}, [tabname, setTabnameError])
const validateTabname = useCallback(
(tabname: string): { error?: string; result?: string } => {
if (!tabname) {
return { error: `Please enter ${label.toLocaleLowerCase()} name.` };
return { error: `Please enter ${label.toLocaleLowerCase()} name.` }
}
let ext = (tabname.includes(".") && tabname.split(".").pop()) || "";
let ext = (tabname.includes('.') && tabname.split('.').pop()) || ''
if (!ext && defaultExtension) {
ext = defaultExtension;
tabname = `${tabname}.${defaultExtension}`;
ext = defaultExtension
tabname = `${tabname}.${defaultExtension}`
}
if (tabs.find(tab => tab.header === tabname)) {
return { error: `${capitalize(label)} name already exists.` };
return { error: `${capitalize(label)} name already exists.` }
}
if (extensionRequired && !ext) {
return { error: "File extension is required!" };
return { error: 'File extension is required!' }
}
if (allowedExtensions && !allowedExtensions.includes(ext)) {
return { error: "This file extension is not allowed!" };
return { error: 'This file extension is not allowed!' }
}
if (
headerExtraValidation &&
!tabname.match(headerExtraValidation.regex)
) {
return { error: headerExtraValidation.error };
if (headerExtraValidation && !tabname.match(headerExtraValidation.regex)) {
return { error: headerExtraValidation.error }
}
return { result: tabname };
return { result: tabname }
},
[
allowedExtensions,
defaultExtension,
extensionRequired,
headerExtraValidation,
label,
tabs,
]
);
[allowedExtensions, defaultExtension, extensionRequired, headerExtraValidation, label, tabs]
)
const handleActiveChange = useCallback(
(idx: number, header?: string) => {
setActive(idx);
onChangeActive?.(idx, header);
setActive(idx)
onChangeActive?.(idx, header)
},
[onChangeActive]
);
)
const handleRenameTab = useCallback(() => {
if (renamingTab === null) return;
if (renamingTab === null) return
const res = validateTabname(tabname);
const res = validateTabname(tabname)
if (res.error) {
setTabnameError(`Error: ${res.error}`);
return;
setTabnameError(`Error: ${res.error}`)
return
}
const { result: nwName = tabname } = res;
const { result: nwName = tabname } = res
setRenamingTab(null);
setTabname("");
setRenamingTab(null)
setTabname('')
const oldName = tabs[renamingTab]?.header;
onRenameTab?.(renamingTab, nwName, oldName);
const oldName = tabs[renamingTab]?.header
onRenameTab?.(renamingTab, nwName, oldName)
handleActiveChange(renamingTab, nwName);
}, [
handleActiveChange,
onRenameTab,
renamingTab,
tabname,
tabs,
validateTabname,
]);
handleActiveChange(renamingTab, nwName)
}, [handleActiveChange, onRenameTab, renamingTab, tabname, tabs, validateTabname])
const handleCreateTab = useCallback(() => {
const res = validateTabname(tabname);
const res = validateTabname(tabname)
if (res.error) {
setTabnameError(`Error: ${res.error}`);
return;
setTabnameError(`Error: ${res.error}`)
return
}
const { result: _tabname = tabname } = res;
const { result: _tabname = tabname } = res
setIsNewtabDialogOpen(false);
setTabname("");
setIsNewtabDialogOpen(false)
setTabname('')
onCreateNewTab?.(_tabname);
onCreateNewTab?.(_tabname)
handleActiveChange(tabs.length, _tabname);
}, [
validateTabname,
tabname,
onCreateNewTab,
handleActiveChange,
tabs.length,
]);
handleActiveChange(tabs.length, _tabname)
}, [validateTabname, tabname, onCreateNewTab, handleActiveChange, tabs.length])
const handleCloseTab = useCallback(
(idx: number) => {
onCloseTab?.(idx, tabs[idx].header);
onCloseTab?.(idx, tabs[idx].header)
if (idx <= active && active !== 0) {
const nwActive = active - 1
handleActiveChange(nwActive, tabs[nwActive].header);
handleActiveChange(nwActive, tabs[nwActive].header)
}
},
[active, handleActiveChange, onCloseTab, tabs]
);
)
const closeOption = (idx: number): Nullable<ContentMenuOption> =>
onCloseTab && {
type: "text",
label: "Close",
key: "close",
onSelect: () => handleCloseTab(idx),
};
type: 'text',
label: 'Close',
key: 'close',
onSelect: () => handleCloseTab(idx)
}
const renameOption = (idx: number, tab: TabProps): Nullable<ContentMenuOption> => {
return (
onRenameTab && !tab.renameDisabled && {
type: "text",
label: "Rename",
key: "rename",
onSelect: () => setRenamingTab(idx),
onRenameTab &&
!tab.renameDisabled && {
type: 'text',
label: 'Rename',
key: 'rename',
onSelect: () => setRenamingTab(idx)
}
);
)
}
return (
@@ -228,21 +200,19 @@ export const Tabs = ({
{!headless && (
<Stack
css={{
gap: "$3",
gap: '$3',
flex: 1,
flexWrap: "nowrap",
marginBottom: "$2",
width: "100%",
overflow: "auto",
flexWrap: 'nowrap',
marginBottom: '$2',
width: '100%',
overflow: 'auto'
}}
>
{tabs.map((tab, idx) => (
<ContextMenu
key={tab.header}
options={
[closeOption(idx), renameOption(idx, tab)].filter(
Boolean
) as ContentMenuOption[]
[closeOption(idx), renameOption(idx, tab)].filter(Boolean) as ContentMenuOption[]
}
>
<Button
@@ -253,11 +223,11 @@ export const Tabs = ({
outline={active !== idx}
size="sm"
css={{
"&:hover": {
'&:hover': {
span: {
visibility: "visible",
},
},
visibility: 'visible'
}
}
}}
>
{tab.header || idx}
@@ -265,19 +235,19 @@ export const Tabs = ({
<Box
as="span"
css={{
display: "flex",
p: "2px",
borderRadius: "$full",
mr: "-4px",
"&:hover": {
display: 'flex',
p: '2px',
borderRadius: '$full',
mr: '-4px',
'&:hover': {
// boxSizing: "0px 0px 1px",
backgroundColor: "$mauve2",
color: "$mauve12",
},
backgroundColor: '$mauve2',
color: '$mauve12'
}
}}
onClick={(ev: React.MouseEvent<HTMLElement>) => {
ev.stopPropagation();
handleCloseTab(idx);
ev.stopPropagation()
handleCloseTab(idx)
}}
>
<X size="9px" weight="bold" />
@@ -287,32 +257,22 @@ export const Tabs = ({
</ContextMenu>
))}
{onCreateNewTab && (
<Dialog
open={isNewtabDialogOpen}
onOpenChange={setIsNewtabDialogOpen}
>
<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 ${label.toLocaleLowerCase()}`}
<Button ghost size="sm" css={{ alignItems: 'center', px: '$2', mr: '$3' }}>
<Plus size="16px" /> {tabs.length === 0 && `Add new ${label.toLocaleLowerCase()}`}
</Button>
</DialogTrigger>
<DialogContent>
<DialogTitle>
Create new {label.toLocaleLowerCase()}
</DialogTitle>
<DialogTitle>Create new {label.toLocaleLowerCase()}</DialogTitle>
<DialogDescription>
<Label>{label} name</Label>
<Input
value={tabname}
onChange={e => setTabname(e.target.value)}
onKeyPress={e => {
if (e.key === "Enter") {
handleCreateTab();
if (e.key === 'Enter') {
handleCreateTab()
}
}}
/>
@@ -322,8 +282,8 @@ export const Tabs = ({
<Flex
css={{
marginTop: 25,
justifyContent: "flex-end",
gap: "$3",
justifyContent: 'flex-end',
gap: '$3'
}}
>
<DialogClose asChild>
@@ -334,7 +294,7 @@ export const Tabs = ({
</Button>
</Flex>
<DialogClose asChild>
<Box css={{ position: "absolute", top: "$3", right: "$3" }}>
<Box css={{ position: 'absolute', top: '$3', right: '$3' }}>
<X size="20px" />
</Box>
</DialogClose>
@@ -342,10 +302,7 @@ export const Tabs = ({
</Dialog>
)}
{onRenameTab && (
<Dialog
open={renamingTab !== null}
onOpenChange={() => setRenamingTab(null)}
>
<Dialog open={renamingTab !== null} onOpenChange={() => setRenamingTab(null)}>
<DialogContent>
<DialogTitle>
Rename <Pre>{tabs[renamingTab || 0]?.header}</Pre>
@@ -356,8 +313,8 @@ export const Tabs = ({
value={tabname}
onChange={e => setTabname(e.target.value)}
onKeyPress={e => {
if (e.key === "Enter") {
handleRenameTab();
if (e.key === 'Enter') {
handleRenameTab()
}
}}
/>
@@ -367,8 +324,8 @@ export const Tabs = ({
<Flex
css={{
marginTop: 25,
justifyContent: "flex-end",
gap: "$3",
justifyContent: 'flex-end',
gap: '$3'
}}
>
<DialogClose asChild>
@@ -379,7 +336,7 @@ export const Tabs = ({
</Button>
</Flex>
<DialogClose asChild>
<Box css={{ position: "absolute", top: "$3", right: "$3" }}>
<Box css={{ position: 'absolute', top: '$3', right: '$3' }}>
<X size="20px" />
</Box>
</DialogClose>
@@ -392,28 +349,26 @@ export const Tabs = ({
? tabs.map((tab, idx) => {
// TODO Maybe rule out fragments as children
if (!isValidElement(tab.children)) {
if (active !== idx) return null;
return 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;
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,
display: active !== idx ? 'none' : undefined
}}
/>
);
)
})
: tabs[active] && (
<Fragment key={tabs[active].header || active}>
{tabs[active].children}
</Fragment>
<Fragment key={tabs[active].header || active}>{tabs[active].children}</Fragment>
)}
</>
);
};
)
}

View File

@@ -1,41 +1,41 @@
import { styled } from "../stitches.config";
import { styled } from '../stitches.config'
const Text = styled("span", {
fontFamily: "$body",
lineHeight: "$body",
color: "$text",
const Text = styled('span', {
fontFamily: '$body',
lineHeight: '$body',
color: '$text',
variants: {
small: {
true: {
fontSize: "$xs",
},
fontSize: '$xs'
}
},
muted: {
true: {
color: "$mauve9",
},
color: '$mauve9'
}
},
error: {
true: {
color: "$error",
},
color: '$error'
}
},
warning: {
true: {
color: "$warning",
},
color: '$warning'
}
},
monospace: {
true: {
fontFamily: "$monospace",
},
fontFamily: '$monospace'
}
},
block: {
true: {
display: "block",
},
},
},
});
display: 'block'
}
}
}
})
export default Text;
export default Text

View File

@@ -1,115 +1,113 @@
import { styled } from "../stitches.config";
import { styled } from '../stitches.config'
export const Textarea = styled("textarea", {
export const Textarea = styled('textarea', {
// Reset
appearance: "none",
borderWidth: "0",
boxSizing: "border-box",
fontFamily: "inherit",
outline: "none",
width: "100%",
flex: "1",
backgroundColor: "$mauve4",
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
borderRadius: "$sm",
p: "$2",
fontSize: "$md",
appearance: 'none',
borderWidth: '0',
boxSizing: 'border-box',
fontFamily: 'inherit',
outline: 'none',
width: '100%',
flex: '1',
backgroundColor: '$mauve4',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '$sm',
p: '$2',
fontSize: '$md',
lineHeight: 1,
color: "$mauve12",
color: '$mauve12',
boxShadow: `0 0 0 1px $colors$mauve8`,
WebkitTapHighlightColor: "rgba(0,0,0,0)",
"&::before": {
boxSizing: "border-box",
WebkitTapHighlightColor: 'rgba(0,0,0,0)',
'&::before': {
boxSizing: 'border-box'
},
"&::after": {
boxSizing: "border-box",
'&::after': {
boxSizing: 'border-box'
},
fontVariantNumeric: "tabular-nums",
fontVariantNumeric: 'tabular-nums',
"&:-webkit-autofill": {
boxShadow: "inset 0 0 0 1px $colors$blue6, inset 0 0 0 100px $colors$blue3",
'&:-webkit-autofill': {
boxShadow: 'inset 0 0 0 1px $colors$blue6, inset 0 0 0 100px $colors$blue3'
},
"&:-webkit-autofill::first-line": {
fontFamily: "$untitled",
color: "$mauve12",
'&:-webkit-autofill::first-line': {
fontFamily: '$untitled',
color: '$mauve12'
},
"&:focus": {
boxShadow: `0 0 0 1px $colors$mauve10`,
"&:-webkit-autofill": {
'&:focus': {
boxShadow: `0 0 0 1px $colors$mauve10`,
'&:-webkit-autofill': {
boxShadow: `0 0 0 1px $colors$mauve10`
}
},
'&::placeholder': {
color: '$mauve9'
},
"&::placeholder": {
color: "$mauve9",
},
"&:disabled": {
pointerEvents: "none",
backgroundColor: "$mauve2",
color: "$mauve8",
cursor: "not-allowed",
"&::placeholder": {
color: "$mauve7",
},
'&:disabled': {
pointerEvents: 'none',
backgroundColor: '$mauve2',
color: '$mauve8',
cursor: 'not-allowed',
'&::placeholder': {
color: '$mauve7'
}
},
variants: {
variant: {
ghost: {
boxShadow: "none",
backgroundColor: "transparent",
"@hover": {
"&:hover": {
boxShadow: "inset 0 0 0 1px $colors$mauve7",
boxShadow: 'none',
backgroundColor: 'transparent',
'@hover': {
'&:hover': {
boxShadow: 'inset 0 0 0 1px $colors$mauve7'
}
},
'&:focus': {
backgroundColor: '$loContrast',
boxShadow: `0 0 0 1px $colors$mauve10`
},
"&:focus": {
backgroundColor: "$loContrast",
boxShadow: `0 0 0 1px $colors$mauve10`,
},
"&:disabled": {
backgroundColor: "transparent",
},
"&:read-only": {
backgroundColor: "transparent",
'&:disabled': {
backgroundColor: 'transparent'
},
'&:read-only': {
backgroundColor: 'transparent'
}
},
deep: {
backgroundColor: "$deep",
boxShadow: "none",
},
backgroundColor: '$deep',
boxShadow: 'none'
}
},
state: {
invalid: {
boxShadow: "inset 0 0 0 1px $colors$crimson7",
"&:focus": {
boxShadow:
"inset 0px 0px 0px 1px $colors$crimson8, 0px 0px 0px 1px $colors$crimson8",
},
boxShadow: 'inset 0 0 0 1px $colors$crimson7',
'&:focus': {
boxShadow: 'inset 0px 0px 0px 1px $colors$crimson8, 0px 0px 0px 1px $colors$crimson8'
}
},
valid: {
boxShadow: "inset 0 0 0 1px $colors$grass7",
"&:focus": {
boxShadow:
"inset 0px 0px 0px 1px $colors$grass8, 0px 0px 0px 1px $colors$grass8",
},
},
boxShadow: 'inset 0 0 0 1px $colors$grass7',
'&:focus': {
boxShadow: 'inset 0px 0px 0px 1px $colors$grass8, 0px 0px 0px 1px $colors$grass8'
}
}
},
cursor: {
default: {
cursor: "default",
"&:focus": {
cursor: "text",
},
cursor: 'default',
'&:focus': {
cursor: 'text'
}
},
text: {
cursor: "text",
},
},
},
});
cursor: 'text'
}
}
}
})
export default Textarea;
export default Textarea

View File

@@ -1,34 +1,34 @@
import { useState, useEffect } from "react";
import { useTheme } from "next-themes";
import { Sun, Moon } from "phosphor-react";
import { useState, useEffect } from 'react'
import { useTheme } from 'next-themes'
import { Sun, Moon } from 'phosphor-react'
import Button from "./Button";
import Button from './Button'
const ThemeChanger = () => {
const { theme, setTheme } = useTheme();
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
const { theme, setTheme } = useTheme()
const [mounted, setMounted] = useState(false)
useEffect(() => setMounted(true), [])
if (!mounted) return null;
if (!mounted) return null
return (
<Button
outline
onClick={() => {
setTheme(theme && theme === "light" ? "dark" : "light");
setTheme(theme && theme === 'light' ? 'dark' : 'light')
}}
css={{
display: "flex",
marginLeft: "auto",
cursor: "pointer",
alignItems: "center",
justifyContent: "center",
color: "$muted",
display: 'flex',
marginLeft: 'auto',
cursor: 'pointer',
alignItems: 'center',
justifyContent: 'center',
color: '$muted'
}}
>
{theme === "dark" ? <Sun size="15px" /> : <Moon size="15px" />}
{theme === 'dark' ? <Sun size="15px" /> : <Moon size="15px" />}
</Button>
);
};
)
}
export default ThemeChanger;
export default ThemeChanger

View File

@@ -1,72 +1,69 @@
import React from "react";
import { styled, keyframes } from "../stitches.config";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import React from 'react'
import { styled, keyframes } from '../stitches.config'
import * as TooltipPrimitive from '@radix-ui/react-tooltip'
const slideUpAndFade = keyframes({
"0%": { opacity: 0, transform: "translateY(2px)" },
"100%": { opacity: 1, transform: "translateY(0)" },
});
'0%': { opacity: 0, transform: 'translateY(2px)' },
'100%': { opacity: 1, transform: 'translateY(0)' }
})
const slideRightAndFade = keyframes({
"0%": { opacity: 0, transform: "translateX(-2px)" },
"100%": { opacity: 1, transform: "translateX(0)" },
});
'0%': { opacity: 0, transform: 'translateX(-2px)' },
'100%': { opacity: 1, transform: 'translateX(0)' }
})
const slideDownAndFade = keyframes({
"0%": { opacity: 0, transform: "translateY(-2px)" },
"100%": { opacity: 1, transform: "translateY(0)" },
});
'0%': { opacity: 0, transform: 'translateY(-2px)' },
'100%': { opacity: 1, transform: 'translateY(0)' }
})
const slideLeftAndFade = keyframes({
"0%": { opacity: 0, transform: "translateX(2px)" },
"100%": { opacity: 1, transform: "translateX(0)" },
});
'0%': { opacity: 0, transform: 'translateX(2px)' },
'100%': { opacity: 1, transform: 'translateX(0)' }
})
const StyledContent = styled(TooltipPrimitive.Content, {
borderRadius: 4,
padding: "$2 $3",
padding: '$2 $3',
fontSize: 12,
lineHeight: 1,
color: "$text",
backgroundColor: "$background",
boxShadow:
"hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px",
"@media (prefers-reduced-motion: no-preference)": {
animationDuration: "400ms",
animationTimingFunction: "cubic-bezier(0.16, 1, 0.3, 1)",
animationFillMode: "forwards",
willChange: "transform, opacity",
color: '$text',
backgroundColor: '$background',
boxShadow: 'hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px',
'@media (prefers-reduced-motion: no-preference)': {
animationDuration: '400ms',
animationTimingFunction: 'cubic-bezier(0.16, 1, 0.3, 1)',
animationFillMode: 'forwards',
willChange: 'transform, opacity',
'&[data-state="delayed-open"]': {
'&[data-side="top"]': { animationName: slideDownAndFade },
'&[data-side="right"]': { animationName: slideLeftAndFade },
'&[data-side="bottom"]': { animationName: slideUpAndFade },
'&[data-side="left"]': { animationName: slideRightAndFade },
'&[data-side="left"]': { animationName: slideRightAndFade }
}
},
},
".dark &": {
'.dark &': {
boxShadow:
"0px 0px 10px 2px rgba(0,0,0,.45), hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px",
'0px 0px 10px 2px rgba(0,0,0,.45), hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px'
},
".light &": {
'.light &': {
boxShadow:
"0px 0px 10px 2px rgba(0,0,0,.25), hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px",
},
});
'0px 0px 10px 2px rgba(0,0,0,.25), hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px'
}
})
const StyledArrow = styled(TooltipPrimitive.Arrow, {
fill: "$background",
});
fill: '$background'
})
interface ITooltip {
content: string;
open?: boolean;
defaultOpen?: boolean;
onOpenChange?: (open: boolean) => void;
content: string
open?: boolean
defaultOpen?: boolean
onOpenChange?: (open: boolean) => void
}
const Tooltip: React.FC<
React.ComponentProps<typeof StyledContent> & ITooltip
> = ({
const Tooltip: React.FC<React.ComponentProps<typeof StyledContent> & ITooltip> = ({
children,
content,
open,
@@ -75,18 +72,14 @@ const Tooltip: React.FC<
...rest
}) => {
return (
<TooltipPrimitive.Root
open={open}
defaultOpen={defaultOpen}
onOpenChange={onOpenChange}
>
<TooltipPrimitive.Root open={open} defaultOpen={defaultOpen} onOpenChange={onOpenChange}>
<TooltipPrimitive.Trigger asChild>{children}</TooltipPrimitive.Trigger>
<StyledContent side="bottom" align="center" {...rest}>
<div dangerouslySetInnerHTML={{ __html: content }} />
<StyledArrow offset={5} width={11} height={5} />
</StyledContent>
</TooltipPrimitive.Root>
);
};
)
}
export default Tooltip;
export default Tooltip

View File

@@ -1,7 +1,7 @@
import { Play } from "phosphor-react";
import { FC, useCallback, useEffect } from "react";
import { useSnapshot } from "valtio";
import state from "../../state";
import { Play } from 'phosphor-react'
import { FC, useCallback, useEffect } from 'react'
import { useSnapshot } from 'valtio'
import state from '../../state'
import {
defaultTransactionType,
getTxFields,
@@ -9,122 +9,100 @@ import {
prepareState,
prepareTransaction,
SelectOption,
TransactionState,
} from "../../state/transactions";
import { sendTransaction } from "../../state/actions";
import Box from "../Box";
import Button from "../Button";
import Flex from "../Flex";
import { TxJson } from "./json";
import { TxUI } from "./ui";
import { default as _estimateFee } from "../../utils/estimateFee";
import toast from "react-hot-toast";
TransactionState
} from '../../state/transactions'
import { sendTransaction } from '../../state/actions'
import Box from '../Box'
import Button from '../Button'
import Flex from '../Flex'
import { TxJson } from './json'
import { TxUI } from './ui'
import { default as _estimateFee } from '../../utils/estimateFee'
import toast from 'react-hot-toast'
export interface TransactionProps {
header: string;
state: TransactionState;
header: string
state: TransactionState
}
const Transaction: FC<TransactionProps> = ({
header,
state: txState,
...props
}) => {
const { accounts, editorSettings } = useSnapshot(state);
const {
selectedAccount,
selectedTransaction,
txIsDisabled,
txIsLoading,
viewType,
editorValue,
} = txState;
const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props }) => {
const { accounts, editorSettings } = useSnapshot(state)
const { selectedAccount, selectedTransaction, txIsDisabled, txIsLoading, viewType, editorValue } =
txState
const setState = useCallback(
(pTx?: Partial<TransactionState>) => {
return modifyTxState(header, pTx);
return modifyTxState(header, pTx)
},
[header]
);
)
const prepareOptions = useCallback(
(state: Partial<TransactionState> = txState) => {
const {
selectedTransaction,
selectedDestAccount,
selectedAccount,
txFields,
} = state;
const { selectedTransaction, selectedDestAccount, selectedAccount, txFields } = state
const TransactionType = selectedTransaction?.value || null;
const Destination = selectedDestAccount?.value || txFields?.Destination;
const Account = selectedAccount?.value || null;
const TransactionType = selectedTransaction?.value || null
const Destination = selectedDestAccount?.value || txFields?.Destination
const Account = selectedAccount?.value || null
return prepareTransaction({
...txFields,
TransactionType,
Destination,
Account,
});
Account
})
},
[txState]
);
)
useEffect(() => {
const transactionType = selectedTransaction?.value;
const account = selectedAccount?.value;
const transactionType = selectedTransaction?.value
const account = selectedAccount?.value
if (!account || !transactionType || txIsLoading) {
setState({ txIsDisabled: true });
setState({ txIsDisabled: true })
} else {
setState({ txIsDisabled: false });
setState({ txIsDisabled: false })
}
}, [
selectedAccount?.value,
selectedTransaction?.value,
setState,
txIsLoading,
]);
}, [selectedAccount?.value, selectedTransaction?.value, setState, txIsLoading])
const submitTest = useCallback(async () => {
let st: TransactionState | undefined;
const tt = txState.selectedTransaction?.value;
if (viewType === "json") {
let st: TransactionState | undefined
const tt = txState.selectedTransaction?.value
if (viewType === 'json') {
// save the editor state first
const pst = prepareState(editorValue || "", tt);
if (!pst) return;
const pst = prepareState(editorValue || '', tt)
if (!pst) return
st = setState(pst);
st = setState(pst)
}
const account = accounts.find(
acc => acc.address === selectedAccount?.value
);
if (txIsDisabled) return;
const account = accounts.find(acc => acc.address === selectedAccount?.value)
if (txIsDisabled) return
setState({ txIsLoading: true });
const logPrefix = header ? `${header.split(".")[0]}: ` : undefined;
setState({ txIsLoading: true })
const logPrefix = header ? `${header.split('.')[0]}: ` : undefined
try {
if (!account) {
throw Error("Account must be selected from imported accounts!");
throw Error('Account must be selected from imported accounts!')
}
const options = prepareOptions(st);
const options = prepareOptions(st)
const fields = getTxFields(options.TransactionType);
const fields = getTxFields(options.TransactionType)
if (fields.Destination && !options.Destination) {
throw Error("Destination account is required!");
throw Error('Destination account is required!')
}
await sendTransaction(account, options, { logPrefix });
await sendTransaction(account, options, { logPrefix })
} catch (error) {
console.error(error);
console.error(error)
if (error instanceof Error) {
state.transactionLogs.push({
type: "error",
message: `${logPrefix}${error.message}`,
});
type: 'error',
message: `${logPrefix}${error.message}`
})
}
}
setState({ txIsLoading: false });
setState({ txIsLoading: false })
}, [
viewType,
accounts,
@@ -134,71 +112,65 @@ const Transaction: FC<TransactionProps> = ({
editorValue,
txState,
selectedAccount?.value,
prepareOptions,
]);
prepareOptions
])
const getJsonString = useCallback(
(state?: Partial<TransactionState>) =>
JSON.stringify(
prepareOptions?.(state) || {},
null,
editorSettings.tabSize
),
JSON.stringify(prepareOptions?.(state) || {}, null, editorSettings.tabSize),
[editorSettings.tabSize, prepareOptions]
);
)
const resetState = useCallback(
(transactionType: SelectOption | undefined = defaultTransactionType) => {
const fields = getTxFields(transactionType?.value);
const fields = getTxFields(transactionType?.value)
const nwState: Partial<TransactionState> = {
viewType,
selectedTransaction: transactionType,
};
selectedTransaction: transactionType
}
if (fields.Destination !== undefined) {
nwState.selectedDestAccount = null;
fields.Destination = "";
nwState.selectedDestAccount = null
fields.Destination = ''
} else {
fields.Destination = undefined;
fields.Destination = undefined
}
nwState.txFields = fields;
nwState.txFields = fields
const state = modifyTxState(header, nwState, { replaceState: true });
const editorValue = getJsonString(state);
return setState({ editorValue });
const state = modifyTxState(header, nwState, { replaceState: true })
const editorValue = getJsonString(state)
return setState({ editorValue })
},
[getJsonString, header, setState, viewType]
);
)
const estimateFee = useCallback(
async (st?: TransactionState, opts?: { silent?: boolean }) => {
const state = st || txState;
const ptx = prepareOptions(state);
const account = accounts.find(
acc => acc.address === state.selectedAccount?.value
);
const state = st || txState
const ptx = prepareOptions(state)
const account = accounts.find(acc => acc.address === state.selectedAccount?.value)
if (!account) {
if (!opts?.silent) {
toast.error("Please select account from the list.");
toast.error('Please select account from the list.')
}
return;
return
}
ptx.Account = account.address;
ptx.Sequence = account.sequence;
ptx.Account = account.address
ptx.Sequence = account.sequence
const res = await _estimateFee(ptx, account, opts);
const fee = res?.base_fee;
setState({ estimatedFee: fee });
return fee;
const res = await _estimateFee(ptx, account, opts)
const fee = res?.base_fee
setState({ estimatedFee: fee })
return fee
},
[accounts, prepareOptions, setState, txState]
);
)
return (
<Box css={{ position: "relative", height: "calc(100% - 28px)" }} {...props}>
{viewType === "json" ? (
<Box css={{ position: 'relative', height: 'calc(100% - 28px)' }} {...props}>
{viewType === 'json' ? (
<TxJson
getJsonString={getJsonString}
header={header}
@@ -212,26 +184,26 @@ const Transaction: FC<TransactionProps> = ({
<Flex
row
css={{
justifyContent: "space-between",
position: "absolute",
justifyContent: 'space-between',
position: 'absolute',
left: 0,
bottom: 0,
width: "100%",
mb: "$1",
width: '100%',
mb: '$1'
}}
>
<Button
onClick={() => {
if (viewType === "ui") {
setState({ viewType: "json" });
} else setState({ viewType: "ui" });
if (viewType === 'ui') {
setState({ viewType: 'json' })
} else setState({ viewType: 'ui' })
}}
outline
>
{viewType === "ui" ? "EDIT AS JSON" : "EXIT JSON MODE"}
{viewType === 'ui' ? 'EDIT AS JSON' : 'EXIT JSON MODE'}
</Button>
<Flex row>
<Button onClick={() => resetState()} outline css={{ mr: "$3" }}>
<Button onClick={() => resetState()} outline css={{ mr: '$3' }}>
RESET
</Button>
<Button
@@ -246,7 +218,7 @@ const Transaction: FC<TransactionProps> = ({
</Flex>
</Flex>
</Box>
);
};
)
}
export default Transaction;
export default Transaction

View File

@@ -1,192 +1,176 @@
import { FC, useCallback, useEffect, useMemo, useState } from "react";
import { useSnapshot } from "valtio";
import state, {
prepareState,
transactionsData,
TransactionState,
} from "../../state";
import Text from "../Text";
import { Flex, Link } from "..";
import { showAlert } from "../../state/actions/showAlert";
import { parseJSON } from "../../utils/json";
import { extractSchemaProps } from "../../utils/schema";
import amountSchema from "../../content/amount-schema.json";
import Monaco from "../Monaco";
import type monaco from "monaco-editor";
import { FC, useCallback, useEffect, useMemo, useState } from 'react'
import { useSnapshot } from 'valtio'
import state, { prepareState, transactionsData, TransactionState } from '../../state'
import Text from '../Text'
import { Flex, Link } from '..'
import { showAlert } from '../../state/actions/showAlert'
import { parseJSON } from '../../utils/json'
import { extractSchemaProps } from '../../utils/schema'
import amountSchema from '../../content/amount-schema.json'
import Monaco from '../Monaco'
import type monaco from 'monaco-editor'
interface JsonProps {
getJsonString?: (state?: Partial<TransactionState>) => string;
header?: string;
setState: (pTx?: Partial<TransactionState> | undefined) => void;
state: TransactionState;
estimateFee?: () => Promise<string | undefined>;
getJsonString?: (state?: Partial<TransactionState>) => string
header?: string
setState: (pTx?: Partial<TransactionState> | undefined) => void
state: TransactionState
estimateFee?: () => Promise<string | undefined>
}
export const TxJson: FC<JsonProps> = ({
getJsonString,
state: txState,
header,
setState,
}) => {
const { editorSettings, accounts } = useSnapshot(state);
const { editorValue, estimatedFee } = txState;
export const TxJson: FC<JsonProps> = ({ getJsonString, state: txState, header, setState }) => {
const { editorSettings, accounts } = useSnapshot(state)
const { editorValue, estimatedFee } = txState
const [currTxType, setCurrTxType] = useState<string | undefined>(
txState.selectedTransaction?.value
);
)
useEffect(() => {
setState({
editorValue: getJsonString?.(),
});
editorValue: getJsonString?.()
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [])
useEffect(() => {
const parsed = parseJSON(editorValue);
if (!parsed) return;
const parsed = parseJSON(editorValue)
if (!parsed) return
const tt = parsed.TransactionType;
const tx = transactionsData.find(t => t.TransactionType === tt);
if (tx) setCurrTxType(tx.TransactionType);
const tt = parsed.TransactionType
const tx = transactionsData.find(t => t.TransactionType === tt)
if (tx) setCurrTxType(tx.TransactionType)
else {
setCurrTxType(undefined);
setCurrTxType(undefined)
}
}, [editorValue]);
}, [editorValue])
const saveState = (value: string, transactionType?: string) => {
const tx = prepareState(value, transactionType);
const tx = prepareState(value, transactionType)
if (tx) {
setState(tx);
setState(tx)
setState({
editorValue: getJsonString?.(tx),
});
editorValue: getJsonString?.(tx)
})
}
}
};
const discardChanges = () => {
showAlert("Confirm", {
body: "Are you sure to discard these changes?",
confirmText: "Yes",
showAlert('Confirm', {
body: 'Are you sure to discard these changes?',
confirmText: 'Yes',
onCancel: () => {},
onConfirm: () => setState({ editorValue: getJsonString?.() }),
});
};
onConfirm: () => setState({ editorValue: getJsonString?.() })
})
}
const onExit = (value: string) => {
const options = parseJSON(value);
const options = parseJSON(value)
if (options) {
saveState(value, currTxType);
return;
saveState(value, currTxType)
return
}
showAlert("Error!", {
showAlert('Error!', {
body: `Malformed Transaction in ${header}, would you like to discard these changes?`,
confirmText: "Discard",
confirmText: 'Discard',
onConfirm: () => setState({ editorValue: getJsonString?.() }),
onCancel: () => setState({ viewType: "json" }),
});
};
onCancel: () => setState({ viewType: 'json' })
})
}
const getSchemas = useCallback(async (): Promise<any[]> => {
const txObj = transactionsData.find(
td => td.TransactionType === currTxType
);
const txObj = transactionsData.find(td => td.TransactionType === currTxType)
let genericSchemaProps: any;
let genericSchemaProps: any
if (txObj) {
genericSchemaProps = extractSchemaProps(txObj);
genericSchemaProps = extractSchemaProps(txObj)
} else {
genericSchemaProps = transactionsData.reduce(
(cumm, td) => ({
...cumm,
...extractSchemaProps(td),
...extractSchemaProps(td)
}),
{}
);
)
}
return [
{
uri: "file:///main-schema.json", // id of the first schema
fileMatch: ["**.json"], // associate with our model
uri: 'file:///main-schema.json', // id of the first schema
fileMatch: ['**.json'], // associate with our model
schema: {
title: header,
type: "object",
required: ["TransactionType", "Account"],
type: 'object',
required: ['TransactionType', 'Account'],
properties: {
...genericSchemaProps,
TransactionType: {
title: "Transaction Type",
enum: transactionsData.map(td => td.TransactionType),
title: 'Transaction Type',
enum: transactionsData.map(td => td.TransactionType)
},
Account: {
$ref: "file:///account-schema.json",
$ref: 'file:///account-schema.json'
},
Destination: {
anyOf: [
{
$ref: "file:///account-schema.json",
$ref: 'file:///account-schema.json'
},
{
type: "string",
title: "Destination Account",
},
],
type: 'string',
title: 'Destination Account'
}
]
},
Amount: {
$ref: "file:///amount-schema.json",
$ref: 'file:///amount-schema.json'
},
Fee: {
$ref: "file:///fee-schema.json",
},
},
},
$ref: 'file:///fee-schema.json'
}
}
}
},
{
uri: "file:///account-schema.json",
uri: 'file:///account-schema.json',
schema: {
type: "string",
title: "Account type",
enum: accounts.map(acc => acc.address),
},
type: 'string',
title: 'Account type',
enum: accounts.map(acc => acc.address)
}
},
{
uri: "file:///fee-schema.json",
uri: 'file:///fee-schema.json',
schema: {
type: "string",
title: "Fee type",
type: 'string',
title: 'Fee type',
const: estimatedFee,
description: estimatedFee
? "Above mentioned value is recommended base fee"
: undefined,
},
description: estimatedFee ? 'Above mentioned value is recommended base fee' : undefined
}
},
{
...amountSchema,
},
];
}, [accounts, currTxType, estimatedFee, header]);
...amountSchema
}
]
}, [accounts, currTxType, estimatedFee, header])
const [monacoInst, setMonacoInst] = useState<typeof monaco>();
const [monacoInst, setMonacoInst] = useState<typeof monaco>()
useEffect(() => {
if (!monacoInst) return;
if (!monacoInst) return
getSchemas().then(schemas => {
monacoInst.languages.json.jsonDefaults.setDiagnosticsOptions({
validate: true,
schemas,
});
});
}, [getSchemas, monacoInst]);
schemas
})
})
}, [getSchemas, monacoInst])
const hasUnsaved = useMemo(
() => editorValue !== getJsonString?.(),
[editorValue, getJsonString]
);
const hasUnsaved = useMemo(() => editorValue !== getJsonString?.(), [editorValue, getJsonString])
return (
<Monaco
rootProps={{
css: { height: "calc(100% - 45px)" },
css: { height: 'calc(100% - 45px)' }
}}
language={"json"}
language={'json'}
id={header}
height="100%"
value={editorValue}
@@ -197,36 +181,29 @@ export const TxJson: FC<JsonProps> = ({
glyphMargin: true,
tabSize: editorSettings.tabSize,
dragAndDrop: true,
fontSize: 14,
});
fontSize: 14
})
setMonacoInst(monaco);
setMonacoInst(monaco)
// register onExit cb
const model = editor.getModel();
model?.onWillDispose(() => onExit(model.getValue()));
const model = editor.getModel()
model?.onWillDispose(() => onExit(model.getValue()))
}}
overlay={
hasUnsaved ? (
<Flex
row
align="center"
css={{ fontSize: "$xs", color: "$textMuted", ml: "auto" }}
>
<Flex row align="center" css={{ fontSize: '$xs', color: '$textMuted', ml: 'auto' }}>
<Text muted small>
This file has unsaved changes.
</Text>
<Link
css={{ ml: "$1" }}
onClick={() => saveState(editorValue || "", currTxType)}
>
<Link css={{ ml: '$1' }} onClick={() => saveState(editorValue || '', currTxType)}>
save
</Link>
<Link css={{ ml: "$1" }} onClick={discardChanges}>
<Link css={{ ml: '$1' }} onClick={discardChanges}>
discard
</Link>
</Flex>
) : undefined
}
/>
);
};
)
}

View File

@@ -1,169 +1,158 @@
import { FC, useCallback, useEffect, useMemo, useState } from "react";
import Container from "../Container";
import Flex from "../Flex";
import Input from "../Input";
import Select from "../Select";
import Text from "../Text";
import { FC, useCallback, useEffect, useMemo, useState } from 'react'
import Container from '../Container'
import Flex from '../Flex'
import Input from '../Input'
import Select from '../Select'
import Text from '../Text'
import {
SelectOption,
TransactionState,
transactionsOptions,
TxFields,
getTxFields,
defaultTransactionType,
} from "../../state/transactions";
import { useSnapshot } from "valtio";
import state from "../../state";
import { streamState } from "../DebugStream";
import { Button } from "..";
import Textarea from "../Textarea";
defaultTransactionType
} from '../../state/transactions'
import { useSnapshot } from 'valtio'
import state from '../../state'
import { streamState } from '../DebugStream'
import { Button } from '..'
import Textarea from '../Textarea'
interface UIProps {
setState: (
pTx?: Partial<TransactionState> | undefined
) => TransactionState | undefined;
state: TransactionState;
estimateFee?: (...arg: any) => Promise<string | undefined>;
setState: (pTx?: Partial<TransactionState> | undefined) => TransactionState | undefined
state: TransactionState
estimateFee?: (...arg: any) => Promise<string | undefined>
}
export const TxUI: FC<UIProps> = ({
state: txState,
setState,
estimateFee,
}) => {
const { accounts } = useSnapshot(state);
const {
selectedAccount,
selectedDestAccount,
selectedTransaction,
txFields,
} = txState;
export const TxUI: FC<UIProps> = ({ state: txState, setState, estimateFee }) => {
const { accounts } = useSnapshot(state)
const { selectedAccount, selectedDestAccount, selectedTransaction, txFields } = txState
const accountOptions: SelectOption[] = accounts.map(acc => ({
label: acc.name,
value: acc.address,
}));
value: acc.address
}))
const destAccountOptions: SelectOption[] = accounts
.map(acc => ({
label: acc.name,
value: acc.address,
value: acc.address
}))
.filter(acc => acc.value !== selectedAccount?.value);
.filter(acc => acc.value !== selectedAccount?.value)
const [feeLoading, setFeeLoading] = useState(false);
const [feeLoading, setFeeLoading] = useState(false)
const resetFields = useCallback(
(tt: string) => {
const fields = getTxFields(tt);
const fields = getTxFields(tt)
if (fields.Destination !== undefined) {
setState({ selectedDestAccount: null });
fields.Destination = "";
setState({ selectedDestAccount: null })
fields.Destination = ''
} else {
fields.Destination = undefined;
fields.Destination = undefined
}
return setState({ txFields: fields });
return setState({ txFields: fields })
},
[setState]
);
)
const handleSetAccount = (acc: SelectOption) => {
setState({ selectedAccount: acc });
streamState.selectedAccount = acc;
};
setState({ selectedAccount: acc })
streamState.selectedAccount = acc
}
const handleSetField = useCallback(
(field: keyof TxFields, value: string, opFields?: TxFields) => {
const fields = opFields || txFields;
const obj = fields[field];
const fields = opFields || txFields
const obj = fields[field]
setState({
txFields: {
...fields,
[field]: typeof obj === "object" ? { ...obj, $value: value } : value,
},
});
[field]: typeof obj === 'object' ? { ...obj, $value: value } : value
}
})
},
[setState, txFields]
);
)
const handleEstimateFee = useCallback(
async (state?: TransactionState, silent?: boolean) => {
setFeeLoading(true);
setFeeLoading(true)
const fee = await estimateFee?.(state, { silent });
if (fee) handleSetField("Fee", fee, state?.txFields);
const fee = await estimateFee?.(state, { silent })
if (fee) handleSetField('Fee', fee, state?.txFields)
setFeeLoading(false);
setFeeLoading(false)
},
[estimateFee, handleSetField]
);
)
const handleChangeTxType = useCallback(
(tt: SelectOption) => {
setState({ selectedTransaction: tt });
setState({ selectedTransaction: tt })
const newState = resetFields(tt.value);
const newState = resetFields(tt.value)
handleEstimateFee(newState, true);
handleEstimateFee(newState, true)
},
[handleEstimateFee, resetFields, setState]
);
)
const switchToJson = () => setState({ viewType: "json" });
const switchToJson = () => setState({ viewType: 'json' })
// default tx
useEffect(() => {
if (selectedTransaction?.value) return;
if (selectedTransaction?.value) return
if (defaultTransactionType) {
handleChangeTxType(defaultTransactionType);
handleChangeTxType(defaultTransactionType)
}
}, [handleChangeTxType, selectedTransaction?.value]);
}, [handleChangeTxType, selectedTransaction?.value])
const fields = useMemo(
() => getTxFields(selectedTransaction?.value),
[selectedTransaction?.value]
);
)
const specialFields = ["TransactionType", "Account"];
const specialFields = ['TransactionType', 'Account']
if (fields.Destination !== undefined) {
specialFields.push("Destination");
specialFields.push('Destination')
}
const otherFields = Object.keys(txFields).filter(
k => !specialFields.includes(k)
) as [keyof TxFields];
const otherFields = Object.keys(txFields).filter(k => !specialFields.includes(k)) as [
keyof TxFields
]
return (
<Container
css={{
p: "$3 01",
fontSize: "$sm",
height: "calc(100% - 45px)",
p: '$3 01',
fontSize: '$sm',
height: 'calc(100% - 45px)'
}}
>
<Flex column fluid css={{ height: "100%", overflowY: "auto", pr: "$1" }}>
<Flex column fluid css={{ height: '100%', overflowY: 'auto', pr: '$1' }}>
<Flex
row
fluid
css={{
justifyContent: "flex-end",
alignItems: "center",
mb: "$3",
mt: "1px",
pr: "1px",
justifyContent: 'flex-end',
alignItems: 'center',
mb: '$3',
mt: '1px',
pr: '1px'
}}
>
<Text muted css={{ mr: "$3" }}>
Transaction type:{" "}
<Text muted css={{ mr: '$3' }}>
Transaction type:{' '}
</Text>
<Select
instanceId="transactionsType"
placeholder="Select transaction type"
options={transactionsOptions}
hideSelectedOptions
css={{ width: "70%" }}
css={{ width: '70%' }}
value={selectedTransaction}
onChange={(tt: any) => handleChangeTxType(tt)}
/>
@@ -172,19 +161,19 @@ export const TxUI: FC<UIProps> = ({
row
fluid
css={{
justifyContent: "flex-end",
alignItems: "center",
mb: "$3",
pr: "1px",
justifyContent: 'flex-end',
alignItems: 'center',
mb: '$3',
pr: '1px'
}}
>
<Text muted css={{ mr: "$3" }}>
Account:{" "}
<Text muted css={{ mr: '$3' }}>
Account:{' '}
</Text>
<Select
instanceId="from-account"
placeholder="Select your account"
css={{ width: "70%" }}
css={{ width: '70%' }}
options={accountOptions}
value={selectedAccount}
onChange={(acc: any) => handleSetAccount(acc)} // TODO make react-select have correct types for acc
@@ -195,19 +184,19 @@ export const TxUI: FC<UIProps> = ({
row
fluid
css={{
justifyContent: "flex-end",
alignItems: "center",
mb: "$3",
pr: "1px",
justifyContent: 'flex-end',
alignItems: 'center',
mb: '$3',
pr: '1px'
}}
>
<Text muted css={{ mr: "$3" }}>
Destination account:{" "}
<Text muted css={{ mr: '$3' }}>
Destination account:{' '}
</Text>
<Select
instanceId="to-account"
placeholder="Select the destination account"
css={{ width: "70%" }}
css={{ width: '70%' }}
options={destAccountOptions}
value={selectedDestAccount}
isClearable
@@ -216,39 +205,37 @@ export const TxUI: FC<UIProps> = ({
</Flex>
)}
{otherFields.map(field => {
let _value = txFields[field];
let _value = txFields[field]
let value: string | undefined;
if (typeof _value === "object") {
if (_value.$type === "json" && typeof _value.$value === "object") {
value = JSON.stringify(_value.$value, null, 2);
let value: string | undefined
if (typeof _value === 'object') {
if (_value.$type === 'json' && typeof _value.$value === 'object') {
value = JSON.stringify(_value.$value, null, 2)
} else {
value = _value.$value.toString();
value = _value.$value.toString()
}
} else {
value = _value?.toString();
value = _value?.toString()
}
const isXrp = typeof _value === "object" && _value.$type === "xrp";
const isJson = typeof _value === "object" && _value.$type === "json";
const isFee = field === "Fee";
let rows = isJson
? (value?.match(/\n/gm)?.length || 0) + 1
: undefined;
if (rows && rows > 5) rows = 5;
const isXrp = typeof _value === 'object' && _value.$type === 'xrp'
const isJson = typeof _value === 'object' && _value.$type === 'json'
const isFee = field === 'Fee'
let rows = isJson ? (value?.match(/\n/gm)?.length || 0) + 1 : undefined
if (rows && rows > 5) rows = 5
return (
<Flex column key={field} css={{ mb: "$2", pr: "1px" }}>
<Flex column key={field} css={{ mb: '$2', pr: '1px' }}>
<Flex
row
fluid
css={{
justifyContent: "flex-end",
alignItems: "center",
position: "relative",
justifyContent: 'flex-end',
alignItems: 'center',
position: 'relative'
}}
>
<Text muted css={{ mr: "$3" }}>
{field + (isXrp ? " (XRP)" : "")}:{" "}
<Text muted css={{ mr: '$3' }}>
{field + (isXrp ? ' (XRP)' : '')}:{' '}
</Text>
{isJson ? (
<Textarea
@@ -257,46 +244,44 @@ export const TxUI: FC<UIProps> = ({
spellCheck={false}
onChange={switchToJson}
css={{
width: "70%",
flex: "inherit",
resize: "vertical",
width: '70%',
flex: 'inherit',
resize: 'vertical'
}}
/>
) : (
<Input
type={isFee ? "number" : "text"}
type={isFee ? 'number' : 'text'}
value={value}
onChange={e => {
if (isFee) {
const val = e.target.value
.replaceAll(".", "")
.replaceAll(",", "");
handleSetField(field, val);
const val = e.target.value.replaceAll('.', '').replaceAll(',', '')
handleSetField(field, val)
} else {
handleSetField(field, e.target.value);
handleSetField(field, e.target.value)
}
}}
onKeyPress={
isFee
? e => {
if (e.key === "." || e.key === ",") {
e.preventDefault();
if (e.key === '.' || e.key === ',') {
e.preventDefault()
}
}
: undefined
}
css={{
width: "70%",
flex: "inherit",
"-moz-appearance": "textfield",
"&::-webkit-outer-spin-button": {
"-webkit-appearance": "none",
margin: 0,
},
"&::-webkit-inner-spin-button ": {
"-webkit-appearance": "none",
margin: 0,
width: '70%',
flex: 'inherit',
'-moz-appearance': 'textfield',
'&::-webkit-outer-spin-button': {
'-webkit-appearance': 'none',
margin: 0
},
'&::-webkit-inner-spin-button ': {
'-webkit-appearance': 'none',
margin: 0
}
}}
/>
)}
@@ -309,12 +294,12 @@ export const TxUI: FC<UIProps> = ({
isDisabled={txState.txIsDisabled}
isLoading={feeLoading}
css={{
position: "absolute",
right: "$2",
fontSize: "$xs",
cursor: "pointer",
alignContent: "center",
display: "flex",
position: 'absolute',
right: '$2',
fontSize: '$xs',
cursor: 'pointer',
alignContent: 'center',
display: 'flex'
}}
onClick={() => handleEstimateFee()}
>
@@ -323,9 +308,9 @@ export const TxUI: FC<UIProps> = ({
)}
</Flex>
</Flex>
);
)
})}
</Flex>
</Container>
);
};
)
}

View File

@@ -1,11 +1,5 @@
const Carbon = () => (
<svg
width="66"
height="32"
viewBox="0 0 66 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<svg width="66" height="32" viewBox="0 0 66 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M33 2L23 15H28L21 24H45L38 15H43L33 2Z"
stroke="#EDEDEF"
@@ -35,6 +29,6 @@ const Carbon = () => (
fill="#EDEDEF"
/>
</svg>
);
)
export default Carbon;
export default Carbon

View File

@@ -1,11 +1,5 @@
const Firewall = () => (
<svg
width="66"
height="32"
viewBox="0 0 66 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<svg width="66" height="32" viewBox="0 0 66 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M33 13V7"
stroke="#EDEDEF"
@@ -70,6 +64,6 @@ const Firewall = () => (
fill="#EDEDEF"
/>
</svg>
);
)
export default Firewall;
export default Firewall

View File

@@ -1,11 +1,5 @@
const Notary = () => (
<svg
width="66"
height="32"
viewBox="0 0 66 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<svg width="66" height="32" viewBox="0 0 66 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M37.5 10.5L26.5 21.5L21 16.0002"
stroke="#EDEDEF"
@@ -35,6 +29,6 @@ const Notary = () => (
fill="#EDEDEF"
/>
</svg>
);
)
export default Notary;
export default Notary

View File

@@ -1,11 +1,5 @@
const Peggy = () => (
<svg
width="66"
height="32"
viewBox="0 0 66 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<svg width="66" height="32" viewBox="0 0 66 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M33 19C40.1797 19 46 16.3137 46 13C46 9.68629 40.1797 7 33 7C25.8203 7 20 9.68629 20 13C20 16.3137 25.8203 19 33 19Z"
stroke="#EDEDEF"
@@ -56,6 +50,6 @@ const Peggy = () => (
fill="#EDEDEF"
/>
</svg>
);
)
export default Peggy;
export default Peggy

View File

@@ -1,11 +1,5 @@
const Starter = () => (
<svg
width="66"
height="32"
viewBox="0 0 66 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<svg width="66" height="32" viewBox="0 0 66 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M42 28H24C23.7347 28 23.4804 27.8946 23.2929 27.7071C23.1053 27.5196 23 27.2652 23 27V5C23 4.73479 23.1053 4.48044 23.2929 4.2929C23.4804 4.10537 23.7347 4.00001 24 4H36.0003L43 11V27C43 27.2652 42.8947 27.5196 42.7071 27.7071C42.5196 27.8946 42.2653 28 42 28V28Z"
stroke="#EDEDEF"
@@ -35,6 +29,6 @@ const Starter = () => (
fill="#EDEDEF"
/>
</svg>
);
)
export default Starter;
export default Starter

View File

@@ -1,16 +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, Label } from "./Input";
export { default as Select } from "./Select";
export * from "./Tabs";
export * from "./AlertDialog/primitive";
export { default as Box } from "./Box";
export { default as Button } from "./Button";
export { default as Pre } from "./Pre";
export { default as ButtonGroup } from "./ButtonGroup";
export * from "./Dialog";
export * from "./DropdownMenu";
export { default as Flex } from './Flex'
export { default as Link } from './Link'
export { default as Container } from './Container'
export { default as Heading } from './Heading'
export { default as Stack } from './Stack'
export { default as Text } from './Text'
export { default as Input, Label } from './Input'
export { default as Select } from './Select'
export * from './Tabs'
export * from './AlertDialog/primitive'
export { default as Box } from './Box'
export { default as Button } from './Button'
export { default as Pre } from './Pre'
export { default as ButtonGroup } from './ButtonGroup'
export * from './Dialog'
export * from './DropdownMenu'

View File

@@ -5,10 +5,7 @@
"schema": {
"anyOf": [
{
"type": [
"number",
"string"
],
"type": ["number", "string"],
"exclusiveMinimum": 0,
"maximum": "100000000000000000"
},
@@ -19,10 +16,7 @@
"description": "Arbitrary currency code for the token. Cannot be XRP."
},
"value": {
"type": [
"string",
"number"
],
"type": ["string", "number"],
"description": "Quoted decimal representation of the amount of the token."
},
"issuer": {

View File

@@ -1,9 +1,9 @@
import { useEffect, useState } from "react";
import { useEffect, useState } from 'react'
// Define general type for useWindowSize hook, which includes width and height
interface Size {
width: number | undefined;
height: number | undefined;
width: number | undefined
height: number | undefined
}
// Hook
@@ -12,25 +12,25 @@ function useWindowSize(): Size {
// Learn more here: https://joshwcomeau.com/react/the-perils-of-rehydration/
const [windowSize, setWindowSize] = useState<Size>({
width: undefined,
height: undefined,
});
height: undefined
})
useEffect(() => {
// Handler to call on window resize
function handleResize() {
// Set window width/height to state
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
height: window.innerHeight
})
}
// Add event listener
window.addEventListener("resize", handleResize);
window.addEventListener('resize', handleResize)
// Call handler right away so state gets updated with initial window size
handleResize();
handleResize()
// Remove event listener on cleanup
return () => window.removeEventListener("resize", handleResize);
}, []); // Empty array ensures that effect is only run on mount
return windowSize;
return () => window.removeEventListener('resize', handleResize)
}, []) // Empty array ensures that effect is only run on mount
return windowSize
}
export default useWindowSize;
export default useWindowSize

View File

@@ -2,19 +2,19 @@
module.exports = {
reactStrictMode: true,
images: {
domains: ["avatars.githubusercontent.com"],
domains: ['avatars.githubusercontent.com']
},
webpack(config, { isServer }) {
config.resolve.alias["vscode"] = require.resolve(
"@codingame/monaco-languageclient/lib/vscode-compatibility"
);
config.resolve.alias['vscode'] = require.resolve(
'@codingame/monaco-languageclient/lib/vscode-compatibility'
)
if (!isServer) {
config.resolve.fallback.fs = false;
config.resolve.fallback.fs = false
}
config.module.rules.push({
test: /\.md$/,
use: "raw-loader",
});
return config;
},
};
use: 'raw-loader'
})
return config
}
}

View File

@@ -7,6 +7,7 @@
"build": "next build",
"start": "next start",
"lint": "next lint",
"format": "prettier --write .",
"postinstall": "patch-package"
},
"dependencies": {
@@ -45,6 +46,7 @@
"patch-package": "^6.4.7",
"phosphor-react": "^1.3.1",
"postinstall-postinstall": "^2.1.0",
"prettier": "^2.7.1",
"re-resizable": "^6.9.1",
"react": "17.0.2",
"react-dom": "17.0.2",

View File

@@ -1,60 +1,54 @@
import { useEffect } from "react";
import "../styles/globals.css";
import type { AppProps } from "next/app";
import Head from "next/head";
import { SessionProvider } from "next-auth/react";
import { ThemeProvider } from "next-themes";
import { Toaster } from "react-hot-toast";
import { useRouter } from "next/router";
import { IdProvider } from "@radix-ui/react-id";
import PlausibleProvider from "next-plausible";
import { useEffect } from 'react'
import '../styles/globals.css'
import type { AppProps } from 'next/app'
import Head from 'next/head'
import { SessionProvider } from 'next-auth/react'
import { ThemeProvider } from 'next-themes'
import { Toaster } from 'react-hot-toast'
import { useRouter } from 'next/router'
import { IdProvider } from '@radix-ui/react-id'
import PlausibleProvider from 'next-plausible'
import { darkTheme, css } from "../stitches.config";
import Navigation from "../components/Navigation";
import { fetchFiles } from "../state/actions";
import state from "../state";
import { darkTheme, css } from '../stitches.config'
import Navigation from '../components/Navigation'
import { fetchFiles } from '../state/actions'
import state from '../state'
import TimeAgo from "javascript-time-ago";
import en from "javascript-time-ago/locale/en.json";
import { useSnapshot } from "valtio";
import Alert from "../components/AlertDialog";
import { Button, Flex } from "../components";
import { ChatCircleText } from "phosphor-react";
import TimeAgo from 'javascript-time-ago'
import en from 'javascript-time-ago/locale/en.json'
import { useSnapshot } from 'valtio'
import Alert from '../components/AlertDialog'
import { Button, Flex } from '../components'
import { ChatCircleText } from 'phosphor-react'
TimeAgo.setDefaultLocale(en.locale);
TimeAgo.addLocale(en);
TimeAgo.setDefaultLocale(en.locale)
TimeAgo.addLocale(en)
function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
const router = useRouter();
const slug = router.query?.slug;
const gistId = (Array.isArray(slug) && slug[0]) ?? null;
const router = useRouter()
const slug = router.query?.slug
const gistId = (Array.isArray(slug) && slug[0]) ?? null
const origin = "https://xrpl-hooks-ide.vercel.app"; // TODO: Change when site is deployed
const shareImg = "/share-image.png";
const origin = 'https://xrpl-hooks-ide.vercel.app' // TODO: Change when site is deployed
const shareImg = '/share-image.png'
const snap = useSnapshot(state);
const snap = useSnapshot(state)
useEffect(() => {
if (gistId && router.isReady) {
fetchFiles(gistId);
fetchFiles(gistId)
} else {
if (
!gistId &&
router.isReady &&
router.pathname.includes("/develop") &&
router.pathname.includes('/develop') &&
!snap.files.length &&
!snap.mainModalShowed
) {
state.mainModalOpen = true;
state.mainModalShowed = true;
state.mainModalOpen = true
state.mainModalShowed = true
}
}
}, [
gistId,
router.isReady,
router.pathname,
snap.files,
snap.mainModalShowed,
]);
}, [gistId, router.isReady, router.pathname, snap.files, snap.mainModalShowed])
return (
<>
@@ -85,37 +79,15 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
<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="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#161618" />
<meta name="application-name" content="XRPL Hooks Builder" />
<meta name="msapplication-TileColor" content="#c10ad0" />
<meta
name="theme-color"
content="#161618"
media="(prefers-color-scheme: dark)"
/>
<meta
name="theme-color"
content="#FDFCFD"
media="(prefers-color-scheme: light)"
/>
<meta name="theme-color" content="#161618" media="(prefers-color-scheme: dark)" />
<meta name="theme-color" content="#FDFCFD" media="(prefers-color-scheme: light)" />
</Head>
<IdProvider>
@@ -125,28 +97,25 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
defaultTheme="dark"
enableSystem={false}
value={{
light: "light",
dark: darkTheme.className,
light: 'light',
dark: darkTheme.className
}}
>
<PlausibleProvider
domain="hooks-builder.xrpl.org"
trackOutboundLinks
>
<PlausibleProvider domain="hooks-builder.xrpl.org" trackOutboundLinks>
<Navigation />
<Component {...pageProps} />
<Toaster
toastOptions={{
className: css({
backgroundColor: "$mauve1",
color: "$mauve10",
fontSize: "$sm",
backgroundColor: '$mauve1',
color: '$mauve10',
fontSize: '$sm',
zIndex: 9999,
".dark &": {
backgroundColor: "$mauve4",
color: "$mauve12",
},
})(),
'.dark &': {
backgroundColor: '$mauve4',
color: '$mauve12'
}
})()
}}
/>
<Alert />
@@ -155,10 +124,10 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
href="https://github.com/XRPLF/Hooks/discussions"
target="_blank"
rel="noopener noreferrer"
css={{ position: "fixed", right: "$4", bottom: "$4" }}
css={{ position: 'fixed', right: '$4', bottom: '$4' }}
>
<Button size="sm" variant="primary" outline>
<ChatCircleText size={14} style={{ marginRight: "0px" }} />
<ChatCircleText size={14} style={{ marginRight: '0px' }} />
Bugs & Discussions
</Button>
</Flex>
@@ -167,6 +136,6 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
</SessionProvider>
</IdProvider>
</>
);
)
}
export default MyApp;
export default MyApp

View File

@@ -1,35 +1,22 @@
import Document, {
Html,
Head,
Main,
NextScript,
DocumentContext,
} from "next/document";
import Document, { Html, Head, Main, NextScript, DocumentContext } from 'next/document'
import { globalStyles, getCssText } from "../stitches.config";
import { globalStyles, getCssText } from '../stitches.config'
class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const initialProps = await Document.getInitialProps(ctx);
const initialProps = await Document.getInitialProps(ctx)
return initialProps;
return initialProps
}
render() {
globalStyles();
globalStyles()
return (
<Html>
<Head>
<style
id="stitches"
dangerouslySetInnerHTML={{ __html: getCssText() }}
/>
<style id="stitches" dangerouslySetInnerHTML={{ __html: getCssText() }} />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossOrigin=""
/>
<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"
@@ -40,8 +27,8 @@ class MyDocument extends Document {
<NextScript />
</body>
</Html>
);
)
}
}
export default MyDocument;
export default MyDocument

View File

@@ -1,12 +1,10 @@
import type { NextRequest, NextFetchEvent } from 'next/server';
import { NextResponse as Response } from 'next/server';
import type { NextRequest, NextFetchEvent } from 'next/server'
import { NextResponse as Response } from 'next/server'
export default function middleware(req: NextRequest, ev: NextFetchEvent) {
if (req.nextUrl.pathname === "/") {
const url = req.nextUrl.clone();
url.pathname = '/develop';
return Response.redirect(url);
if (req.nextUrl.pathname === '/') {
const url = req.nextUrl.clone()
url.pathname = '/develop'
return Response.redirect(url)
}
}

View File

@@ -1,4 +1,4 @@
import NextAuth from "next-auth"
import NextAuth from 'next-auth'
export default NextAuth({
// Configure one or more authentication providers
@@ -10,39 +10,38 @@ export default NextAuth({
// scope: 'user,gist'
// }),
{
id: "github",
name: "GitHub",
type: "oauth",
id: 'github',
name: 'GitHub',
type: 'oauth',
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
authorization: "https://github.com/login/oauth/authorize?scope=read:user+user:email+gist",
token: "https://github.com/login/oauth/access_token",
userinfo: "https://api.github.com/user",
authorization: 'https://github.com/login/oauth/authorize?scope=read:user+user:email+gist',
token: 'https://github.com/login/oauth/access_token',
userinfo: 'https://api.github.com/user',
profile(profile) {
return {
id: profile.id.toString(),
name: profile.name || profile.login,
username: profile.login,
email: profile.email,
image: profile.avatar_url,
image: profile.avatar_url
}
}
},
}
// ...add more providers here
],
callbacks: {
async jwt({ token, user, account, profile, isNewUser }) {
if (account && account.access_token) {
token.accessToken = account.access_token;
token.username = user?.username || '';
token.accessToken = account.access_token
token.username = user?.username || ''
}
return token
},
async session({ session, token }) {
session.accessToken = token.accessToken as string;
session['user']['username'] = token.username as string;
session.accessToken = token.accessToken as string
session['user']['username'] = token.username as string
return session
}
},
}
})

View File

@@ -6,14 +6,13 @@ interface ErrorResponse {
}
export interface Faucet {
address: string;
secret: string;
xrp: number;
hash: string;
code: string;
address: string
secret: string
xrp: number
hash: string
code: string
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Faucet | ErrorResponse>
@@ -21,20 +20,25 @@ export default async function handler(
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed!' })
}
const { account } = req.query;
const ip = Array.isArray(req?.headers?.["x-real-ip"]) ? req?.headers?.["x-real-ip"][0] : req?.headers?.["x-real-ip"];
const { account } = req.query
const ip = Array.isArray(req?.headers?.['x-real-ip'])
? req?.headers?.['x-real-ip'][0]
: req?.headers?.['x-real-ip']
try {
const response = await fetch(`https://${process.env.NEXT_PUBLIC_TESTNET_URL}/newcreds?account=${account ? account : ''}`, {
const response = await fetch(
`https://${process.env.NEXT_PUBLIC_TESTNET_URL}/newcreds?account=${account ? account : ''}`,
{
method: 'POST',
headers: {
'x-forwarded-for': ip || '',
},
});
const json: Faucet | ErrorResponse = await response.json();
if ("error" in json) {
'x-forwarded-for': ip || ''
}
}
)
const json: Faucet | ErrorResponse = await response.json()
if ('error' in json) {
return res.status(429).json(json)
}
return res.status(200).json(json);
return res.status(200).json(json)
} catch (err) {
console.log(err)
return res.status(500).json({ error: 'Server error' })

View File

@@ -5,9 +5,6 @@ type Data = {
name: string
}
export default function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
export default function handler(req: NextApiRequest, res: NextApiResponse<Data>) {
res.status(200).json({ name: 'John Doe' })
}

View File

@@ -1,18 +1,15 @@
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { url, opts } = req.body
const r = await fetch(url, opts);
if (!r.ok) throw (r.statusText)
const r = await fetch(url, opts)
if (!r.ok) throw r.statusText
const data = await r.json()
return res.json(data)
} catch (error) {
console.warn(error)
return res.status(500).json({ message: "Something went wrong!" })
return res.status(500).json({ message: 'Something went wrong!' })
}
}

View File

@@ -1,63 +1,59 @@
import dynamic from "next/dynamic";
import React from "react";
import Split from "react-split";
import { useSnapshot } from "valtio";
import state from "../../state";
import { getSplit, saveSplit } from "../../state/actions/persistSplits";
import dynamic from 'next/dynamic'
import React from 'react'
import Split from 'react-split'
import { useSnapshot } from 'valtio'
import state from '../../state'
import { getSplit, saveSplit } from '../../state/actions/persistSplits'
const DeployEditor = dynamic(() => import("../../components/DeployEditor"), {
ssr: false,
});
const DeployEditor = dynamic(() => import('../../components/DeployEditor'), {
ssr: false
})
const Accounts = dynamic(() => import("../../components/Accounts"), {
ssr: false,
});
const Accounts = dynamic(() => import('../../components/Accounts'), {
ssr: false
})
const LogBox = dynamic(() => import("../../components/LogBox"), {
ssr: false,
});
const LogBox = dynamic(() => import('../../components/LogBox'), {
ssr: false
})
const Deploy = () => {
const { deployLogs } = useSnapshot(state);
const { deployLogs } = useSnapshot(state)
return (
<Split
direction="vertical"
gutterSize={4}
gutterAlign="center"
sizes={getSplit("deployVertical") || [40, 60]}
style={{ height: "calc(100vh - 60px)" }}
onDragEnd={(e) => saveSplit("deployVertical", e)}
sizes={getSplit('deployVertical') || [40, 60]}
style={{ height: 'calc(100vh - 60px)' }}
onDragEnd={e => saveSplit('deployVertical', e)}
>
<main style={{ display: "flex", flex: 1, position: "relative" }}>
<main style={{ display: 'flex', flex: 1, position: 'relative' }}>
<DeployEditor />
</main>
<Split
direction="horizontal"
sizes={getSplit("deployHorizontal") || [50, 50]}
sizes={getSplit('deployHorizontal') || [50, 50]}
minSize={[320, 160]}
gutterSize={4}
gutterAlign="center"
style={{
display: "flex",
flexDirection: "row",
width: "100%",
height: "100%",
display: 'flex',
flexDirection: 'row',
width: '100%',
height: '100%'
}}
onDragEnd={(e) => saveSplit("deployHorizontal", e)}
onDragEnd={e => saveSplit('deployHorizontal', e)}
>
<div style={{ alignItems: "stretch", display: "flex" }}>
<div style={{ alignItems: 'stretch', display: 'flex' }}>
<Accounts />
</div>
<div>
<LogBox
title="Deploy Log"
logs={deployLogs}
clearLog={() => (state.deployLogs = [])}
/>
<LogBox title="Deploy Log" logs={deployLogs} clearLog={() => (state.deployLogs = [])} />
</div>
</Split>
</Split>
);
};
)
}
export default Deploy;
export default Deploy

View File

@@ -1,34 +1,33 @@
import { Label } from "@radix-ui/react-label";
import type { NextPage } from "next";
import dynamic from "next/dynamic";
import { FileJs, Gear, Play } from "phosphor-react";
import Hotkeys from "react-hot-keys";
import Split from "react-split";
import { useSnapshot } from "valtio";
import { ButtonGroup, Flex } from "../../components";
import Box from "../../components/Box";
import Button from "../../components/Button";
import Popover from "../../components/Popover";
import RunScript from "../../components/RunScript";
import state from "../../state";
import { compileCode } from "../../state/actions";
import { getSplit, saveSplit } from "../../state/actions/persistSplits";
import { styled } from "../../stitches.config";
import { Label } from '@radix-ui/react-label'
import type { NextPage } from 'next'
import dynamic from 'next/dynamic'
import { FileJs, Gear, Play } from 'phosphor-react'
import Hotkeys from 'react-hot-keys'
import Split from 'react-split'
import { useSnapshot } from 'valtio'
import { ButtonGroup, Flex } from '../../components'
import Box from '../../components/Box'
import Button from '../../components/Button'
import Popover from '../../components/Popover'
import RunScript from '../../components/RunScript'
import state from '../../state'
import { compileCode } from '../../state/actions'
import { getSplit, saveSplit } from '../../state/actions/persistSplits'
import { styled } from '../../stitches.config'
const HooksEditor = dynamic(() => import("../../components/HooksEditor"), {
ssr: false,
});
const HooksEditor = dynamic(() => import('../../components/HooksEditor'), {
ssr: false
})
const LogBox = dynamic(() => import("../../components/LogBox"), {
ssr: false,
});
const LogBox = dynamic(() => import('../../components/LogBox'), {
ssr: false
})
const OptimizationText = () => (
<span>
Specify which optimization level to use for compiling. For example -O0 means
no optimization: this level compiles the fastest and generates the most
debuggable code. -O2 means moderate level of optimization which enables most
optimizations. Read more about the options from{" "}
Specify which optimization level to use for compiling. For example -O0 means no optimization:
this level compiles the fastest and generates the most debuggable code. -O2 means moderate level
of optimization which enables most optimizations. Read more about the options from{' '}
<a
className="link"
rel="noopener noreferrer"
@@ -39,144 +38,141 @@ const OptimizationText = () => (
</a>
.
</span>
);
)
const StyledOptimizationText = styled(OptimizationText, {
color: "$mauve12 !important",
fontSize: "200px",
"span a.link": {
color: "red",
},
});
color: '$mauve12 !important',
fontSize: '200px',
'span a.link': {
color: 'red'
}
})
const CompilerSettings = () => {
const snap = useSnapshot(state);
const snap = useSnapshot(state)
return (
<Flex css={{ minWidth: 200, flexDirection: "column", gap: "$5" }}>
<Flex css={{ minWidth: 200, flexDirection: 'column', gap: '$5' }}>
<Box>
<Label
style={{
flexDirection: "row",
display: "flex",
flexDirection: 'row',
display: 'flex'
}}
>
Optimization level{" "}
Optimization level{' '}
<Popover
css={{
maxWidth: "240px",
lineHeight: "1.3",
maxWidth: '240px',
lineHeight: '1.3',
a: {
color: "$purple11",
color: '$purple11'
},
".dark &": {
backgroundColor: "$black !important",
'.dark &': {
backgroundColor: '$black !important',
".arrow": {
fill: "$colors$black",
},
},
'.arrow': {
fill: '$colors$black'
}
}
}}
content={<StyledOptimizationText />}
>
<Flex
css={{
position: "relative",
top: "-1px",
ml: "$1",
backgroundColor: "$mauve8",
borderRadius: "$full",
cursor: "pointer",
width: "16px",
height: "16px",
alignItems: "center",
justifyContent: "center",
position: 'relative',
top: '-1px',
ml: '$1',
backgroundColor: '$mauve8',
borderRadius: '$full',
cursor: 'pointer',
width: '16px',
height: '16px',
alignItems: 'center',
justifyContent: 'center'
}}
>
?
</Flex>
</Popover>
</Label>
<ButtonGroup css={{ mt: "$2", fontFamily: "$monospace" }}>
<ButtonGroup css={{ mt: '$2', fontFamily: '$monospace' }}>
<Button
css={{ fontFamily: "$monospace" }}
outline={snap.compileOptions.optimizationLevel !== "-O0"}
onClick={() => (state.compileOptions.optimizationLevel = "-O0")}
css={{ fontFamily: '$monospace' }}
outline={snap.compileOptions.optimizationLevel !== '-O0'}
onClick={() => (state.compileOptions.optimizationLevel = '-O0')}
>
-O0
</Button>
<Button
css={{ fontFamily: "$monospace" }}
outline={snap.compileOptions.optimizationLevel !== "-O1"}
onClick={() => (state.compileOptions.optimizationLevel = "-O1")}
css={{ fontFamily: '$monospace' }}
outline={snap.compileOptions.optimizationLevel !== '-O1'}
onClick={() => (state.compileOptions.optimizationLevel = '-O1')}
>
-O1
</Button>
<Button
css={{ fontFamily: "$monospace" }}
outline={snap.compileOptions.optimizationLevel !== "-O2"}
onClick={() => (state.compileOptions.optimizationLevel = "-O2")}
css={{ fontFamily: '$monospace' }}
outline={snap.compileOptions.optimizationLevel !== '-O2'}
onClick={() => (state.compileOptions.optimizationLevel = '-O2')}
>
-O2
</Button>
<Button
css={{ fontFamily: "$monospace" }}
outline={snap.compileOptions.optimizationLevel !== "-O3"}
onClick={() => (state.compileOptions.optimizationLevel = "-O3")}
css={{ fontFamily: '$monospace' }}
outline={snap.compileOptions.optimizationLevel !== '-O3'}
onClick={() => (state.compileOptions.optimizationLevel = '-O3')}
>
-O3
</Button>
<Button
css={{ fontFamily: "$monospace" }}
outline={snap.compileOptions.optimizationLevel !== "-O4"}
onClick={() => (state.compileOptions.optimizationLevel = "-O4")}
css={{ fontFamily: '$monospace' }}
outline={snap.compileOptions.optimizationLevel !== '-O4'}
onClick={() => (state.compileOptions.optimizationLevel = '-O4')}
>
-O4
</Button>
<Button
css={{ fontFamily: "$monospace" }}
outline={snap.compileOptions.optimizationLevel !== "-Os"}
onClick={() => (state.compileOptions.optimizationLevel = "-Os")}
css={{ fontFamily: '$monospace' }}
outline={snap.compileOptions.optimizationLevel !== '-Os'}
onClick={() => (state.compileOptions.optimizationLevel = '-Os')}
>
-Os
</Button>
</ButtonGroup>
</Box>
</Flex>
);
};
)
}
const Home: NextPage = () => {
const snap = useSnapshot(state);
const snap = useSnapshot(state)
return (
<Split
direction="vertical"
sizes={getSplit("developVertical") || [70, 30]}
sizes={getSplit('developVertical') || [70, 30]}
minSize={[100, 100]}
gutterAlign="center"
gutterSize={4}
style={{ height: "calc(100vh - 60px)" }}
onDragEnd={(e) => saveSplit("developVertical", e)}
style={{ height: 'calc(100vh - 60px)' }}
onDragEnd={e => saveSplit('developVertical', e)}
>
<main style={{ display: "flex", flex: 1, position: "relative" }}>
<main style={{ display: 'flex', flex: 1, position: 'relative' }}>
<HooksEditor />
{snap.files[snap.active]?.name?.split(".")?.[1]?.toLowerCase() ===
"c" && (
{snap.files[snap.active]?.name?.split('.')?.[1]?.toLowerCase() === 'c' && (
<Hotkeys
keyName="command+b,ctrl+b"
onKeyDown={() =>
!snap.compiling && snap.files.length && compileCode(snap.active)
}
onKeyDown={() => !snap.compiling && snap.files.length && compileCode(snap.active)}
>
<Flex
css={{
position: "absolute",
bottom: "$4",
left: "$4",
alignItems: "center",
display: "flex",
cursor: "pointer",
gap: "$2",
position: 'absolute',
bottom: '$4',
left: '$4',
alignItems: 'center',
display: 'flex',
cursor: 'pointer',
gap: '$2'
}}
>
<Button
@@ -190,30 +186,27 @@ const Home: NextPage = () => {
Compile to Wasm
</Button>
<Popover content={<CompilerSettings />}>
<Button variant="primary" css={{ px: "10px" }}>
<Button variant="primary" css={{ px: '10px' }}>
<Gear size="16px" />
</Button>
</Popover>
</Flex>
</Hotkeys>
)}
{snap.files[snap.active]?.name?.split(".")?.[1]?.toLowerCase() ===
"js" && (
{snap.files[snap.active]?.name?.split('.')?.[1]?.toLowerCase() === 'js' && (
<Hotkeys
keyName="command+b,ctrl+b"
onKeyDown={() =>
!snap.compiling && snap.files.length && compileCode(snap.active)
}
onKeyDown={() => !snap.compiling && snap.files.length && compileCode(snap.active)}
>
<Flex
css={{
position: "absolute",
bottom: "$4",
left: "$4",
alignItems: "center",
display: "flex",
cursor: "pointer",
gap: "$2",
position: 'absolute',
bottom: '$4',
left: '$4',
alignItems: 'center',
display: 'flex',
cursor: 'pointer',
gap: '$2'
}}
>
<RunScript file={snap.files[snap.active]} />
@@ -221,26 +214,21 @@ const Home: NextPage = () => {
</Hotkeys>
)}
</main>
<Flex css={{ width: "100%" }}>
<Flex css={{ width: '100%' }}>
<Flex
css={{
flex: 1,
background: "$mauve1",
position: "relative",
borderRight: "1px solid $mauve8",
background: '$mauve1',
position: 'relative',
borderRight: '1px solid $mauve8'
}}
>
<LogBox
title="Development Log"
clearLog={() => (state.logs = [])}
logs={snap.logs}
/>
<LogBox title="Development Log" clearLog={() => (state.logs = [])} logs={snap.logs} />
</Flex>
{snap.files[snap.active]?.name?.split(".")?.[1]?.toLowerCase() ===
"js" && (
{snap.files[snap.active]?.name?.split('.')?.[1]?.toLowerCase() === 'js' && (
<Flex
css={{
flex: 1,
flex: 1
}}
>
<LogBox
@@ -253,7 +241,7 @@ const Home: NextPage = () => {
)}
</Flex>
</Split>
);
};
)
}
export default Home;
export default Home

View File

@@ -1,5 +1,5 @@
const Home = () => {
return <p>homepage</p>;
};
return <p>homepage</p>
}
export default Home;
export default Home

View File

@@ -1,38 +1,37 @@
import { useEffect } from "react";
import { signIn, useSession } from "next-auth/react";
import { useEffect } from 'react'
import { signIn, useSession } from 'next-auth/react'
import Box from "../components/Box";
import Spinner from "../components/Spinner";
import Box from '../components/Box'
import Spinner from '../components/Spinner'
const SignInPage = () => {
const { data: session, status } = useSession();
const { data: session, status } = useSession()
useEffect(() => {
if (status !== "loading" && !session)
void signIn("github", { redirect: false });
if (status !== "loading" && session) window.close();
}, [session, status]);
if (status !== 'loading' && !session) void signIn('github', { redirect: false })
if (status !== 'loading' && session) window.close()
}, [session, status])
return (
<Box
css={{
display: "flex",
backgroundColor: "$mauve1",
position: "absolute",
display: 'flex',
backgroundColor: '$mauve1',
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
left: 0,
zIndex: 9999,
textAlign: "center",
justifyContent: "center",
alignItems: "center",
gap: "$2",
textAlign: 'center',
justifyContent: 'center',
alignItems: 'center',
gap: '$2'
}}
>
Logging in <Spinner />
</Box>
);
};
)
}
export default SignInPage;
export default SignInPage

View File

@@ -1,58 +1,56 @@
import dynamic from "next/dynamic";
import Split from "react-split";
import { useSnapshot } from "valtio";
import { Box, Container, Flex, Tab, Tabs } from "../../components";
import Transaction from "../../components/Transaction";
import state, { renameTxState } from "../../state";
import { getSplit, saveSplit } from "../../state/actions/persistSplits";
import { transactionsState, modifyTxState } from "../../state";
import { useEffect, useState } from "react";
import { FileJs } from "phosphor-react";
import RunScript from "../../components/RunScript";
import dynamic from 'next/dynamic'
import Split from 'react-split'
import { useSnapshot } from 'valtio'
import { Box, Container, Flex, Tab, Tabs } from '../../components'
import Transaction from '../../components/Transaction'
import state, { renameTxState } from '../../state'
import { getSplit, saveSplit } from '../../state/actions/persistSplits'
import { transactionsState, modifyTxState } from '../../state'
import { useEffect, useState } from 'react'
import { FileJs } from 'phosphor-react'
import RunScript from '../../components/RunScript'
const DebugStream = dynamic(() => import("../../components/DebugStream"), {
ssr: false,
});
const DebugStream = dynamic(() => import('../../components/DebugStream'), {
ssr: false
})
const LogBox = dynamic(() => import("../../components/LogBox"), {
ssr: false,
});
const Accounts = dynamic(() => import("../../components/Accounts"), {
ssr: false,
});
const LogBox = dynamic(() => import('../../components/LogBox'), {
ssr: false
})
const Accounts = dynamic(() => import('../../components/Accounts'), {
ssr: false
})
const Test = () => {
// This and useEffect is here to prevent useLayoutEffect warnings from react-split
const [showComponent, setShowComponent] = useState(false);
const { transactionLogs } = useSnapshot(state);
const { transactions, activeHeader } = useSnapshot(transactionsState);
const snap = useSnapshot(state);
const [showComponent, setShowComponent] = useState(false)
const { transactionLogs } = useSnapshot(state)
const { transactions, activeHeader } = useSnapshot(transactionsState)
const snap = useSnapshot(state)
useEffect(() => {
setShowComponent(true);
}, []);
setShowComponent(true)
}, [])
if (!showComponent) {
return null;
return null
}
const hasScripts = Boolean(
snap.files.filter(f => f.name.toLowerCase()?.endsWith(".js")).length
);
const hasScripts = Boolean(snap.files.filter(f => f.name.toLowerCase()?.endsWith('.js')).length)
const renderNav = () => (
<Flex css={{ gap: "$3" }}>
<Flex css={{ gap: '$3' }}>
{snap.files
.filter(f => f.name.endsWith(".js"))
.filter(f => f.name.endsWith('.js'))
.map(file => (
<RunScript file={file} key={file.name} />
))}
</Flex>
);
)
return (
<Container css={{ px: 0 }}>
<Split
direction="vertical"
sizes={
hasScripts && getSplit("testVertical")?.length === 2
hasScripts && getSplit('testVertical')?.length === 2
? [50, 20, 30]
: hasScripts
? [50, 20, 50]
@@ -60,49 +58,45 @@ const Test = () => {
}
gutterSize={4}
gutterAlign="center"
style={{ height: "calc(100vh - 60px)" }}
onDragEnd={e => saveSplit("testVertical", e)}
style={{ height: 'calc(100vh - 60px)' }}
onDragEnd={e => saveSplit('testVertical', e)}
>
<Flex
row
fluid
css={{
justifyContent: "center",
p: "$3 $2",
justifyContent: 'center',
p: '$3 $2'
}}
>
<Split
direction="horizontal"
sizes={getSplit("testHorizontal") || [50, 50]}
sizes={getSplit('testHorizontal') || [50, 50]}
minSize={[180, 320]}
gutterSize={4}
gutterAlign="center"
style={{
display: "flex",
flexDirection: "row",
width: "100%",
height: "100%",
display: 'flex',
flexDirection: 'row',
width: '100%',
height: '100%'
}}
onDragEnd={e => saveSplit("testHorizontal", e)}
onDragEnd={e => saveSplit('testHorizontal', e)}
>
<Box css={{ width: "55%", px: "$2" }}>
<Box css={{ width: '55%', px: '$2' }}>
<Tabs
label="Transaction"
activeHeader={activeHeader}
// TODO make header a required field
onChangeActive={(idx, header) => {
if (header) transactionsState.activeHeader = header;
if (header) transactionsState.activeHeader = header
}}
keepAllAlive
defaultExtension="json"
allowedExtensions={["json"]}
allowedExtensions={['json']}
onCreateNewTab={header => modifyTxState(header, {})}
onRenameTab={(idx, nwName, oldName = "") =>
renameTxState(oldName, nwName)
}
onCloseTab={(idx, header) =>
header && modifyTxState(header, undefined)
}
onRenameTab={(idx, nwName, oldName = '') => renameTxState(oldName, nwName)}
onCloseTab={(idx, header) => header && modifyTxState(header, undefined)}
>
{transactions.map(({ header, state }) => (
<Tab key={header} header={header}>
@@ -111,7 +105,7 @@ const Test = () => {
))}
</Tabs>
</Box>
<Box css={{ width: "45%", mx: "$2", height: "100%" }}>
<Box css={{ width: '45%', mx: '$2', height: '100%' }}>
<Accounts card hideDeployBtn showHookStats />
</Box>
</Split>
@@ -120,9 +114,9 @@ const Test = () => {
<Flex
as="div"
css={{
borderTop: "1px solid $mauve6",
background: "$mauve1",
flexDirection: "column",
borderTop: '1px solid $mauve6',
background: '$mauve1',
flexDirection: 'column'
}}
>
<LogBox
@@ -142,16 +136,16 @@ const Test = () => {
gutterSize={4}
gutterAlign="center"
style={{
display: "flex",
flexDirection: "row",
width: "100%",
height: "100%",
display: 'flex',
flexDirection: 'row',
width: '100%',
height: '100%'
}}
>
<Box
css={{
borderRight: "1px solid $mauve8",
height: "100%",
borderRight: '1px solid $mauve8',
height: '100%'
}}
>
<LogBox
@@ -160,14 +154,14 @@ const Test = () => {
clearLog={() => (state.transactionLogs = [])}
/>
</Box>
<Box css={{ height: "100%" }}>
<Box css={{ height: '100%' }}>
<DebugStream />
</Box>
</Split>
</Flex>
</Split>
</Container>
);
};
)
}
export default Test;
export default Test

8
raw-loader.d.ts vendored
View File

@@ -1,4 +1,4 @@
declare module "*.md" {
const content: string;
export default content;
};
declare module '*.md' {
const content: string
export default content
}

View File

@@ -1,25 +1,24 @@
import toast from "react-hot-toast";
import state, { FaucetAccountRes } from '../index';
import toast from 'react-hot-toast'
import state, { FaucetAccountRes } from '../index'
export const names = [
"Alice",
"Bob",
"Carol",
"Carlos",
"Charlie",
"Dan",
"Dave",
"David",
"Faythe",
"Frank",
"Grace",
"Heidi",
"Judy",
"Olive",
"Peggy",
"Walter",
];
'Alice',
'Bob',
'Carol',
'Carlos',
'Charlie',
'Dan',
'Dave',
'David',
'Faythe',
'Frank',
'Grace',
'Heidi',
'Judy',
'Olive',
'Peggy',
'Walter'
]
/* This function adds faucet account to application global state.
* It calls the /api/faucet endpoint which in send a HTTP POST to
@@ -30,22 +29,22 @@ export const names = [
export const addFaucetAccount = async (name?: string, showToast: boolean = false) => {
if (typeof window === undefined) return
const toastId = showToast ? toast.loading("Creating account") : "";
const toastId = showToast ? toast.loading('Creating account') : ''
const res = await fetch(`${window.location.origin}/api/faucet`, {
method: "POST",
});
const json: FaucetAccountRes | { error: string } = await res.json();
if ("error" in json) {
method: 'POST'
})
const json: FaucetAccountRes | { error: string } = await res.json()
if ('error' in json) {
if (showToast) {
return toast.error(json.error, { id: toastId });
return toast.error(json.error, { id: toastId })
} else {
return;
return
}
} else {
if (showToast) {
toast.success("New account created", { id: toastId });
toast.success('New account created', { id: toastId })
}
const currNames = state.accounts.map(acc => acc.name);
const currNames = state.accounts.map(acc => acc.name)
state.accounts.push({
name: name || names.filter(name => !currNames.includes(name))[0],
xrp: (json.xrp || 0 * 1000000).toString(),
@@ -55,36 +54,35 @@ export const addFaucetAccount = async (name?: string, showToast: boolean = false
hooks: [],
isLoading: false,
version: '2'
});
})
}
};
}
// fetch initial faucets
(async function fetchFaucets() {
;(async function fetchFaucets() {
if (typeof window !== 'undefined') {
if (state.accounts.length === 0) {
await addFaucetAccount();
await addFaucetAccount()
// setTimeout(() => {
// addFaucetAccount();
// }, 10000);
}
}
})();
})()
export const addFunds = async (address: string) => {
const toastId = toast.loading("Requesting funds");
const toastId = toast.loading('Requesting funds')
const res = await fetch(`${window.location.origin}/api/faucet?account=${address}`, {
method: "POST",
});
const json: FaucetAccountRes | { error: string } = await res.json();
if ("error" in json) {
return toast.error(json.error, { id: toastId });
method: 'POST'
})
const json: FaucetAccountRes | { error: string } = await res.json()
if ('error' in json) {
return toast.error(json.error, { id: toastId })
} else {
toast.success(`Funds added (${json.xrp} XRP)`, { id: toastId });
const currAccount = state.accounts.find(acc => acc.address === address);
toast.success(`Funds added (${json.xrp} XRP)`, { id: toastId })
const currAccount = state.accounts.find(acc => acc.address === address)
if (currAccount) {
currAccount.xrp = (Number(currAccount.xrp) + (json.xrp * 1000000)).toString();
currAccount.xrp = (Number(currAccount.xrp) + json.xrp * 1000000).toString()
}
}
}

View File

@@ -1,10 +1,10 @@
import toast from "react-hot-toast";
import Router from 'next/router';
import toast from 'react-hot-toast'
import Router from 'next/router'
import state from "../index";
import { saveFile } from "./saveFile";
import { decodeBinary } from "../../utils/decodeBinary";
import { ref } from "valtio";
import state from '../index'
import { saveFile } from './saveFile'
import { decodeBinary } from '../../utils/decodeBinary'
import { ref } from 'valtio'
/* compileCode sends the code of the active file to compile endpoint
* If all goes well you will get base64 encoded wasm file back with
@@ -14,17 +14,17 @@ import { ref } from "valtio";
*/
export const compileCode = async (activeId: number) => {
// Save the file to global state
saveFile(false, activeId);
saveFile(false, activeId)
if (!process.env.NEXT_PUBLIC_COMPILE_API_ENDPOINT) {
throw Error("Missing env!");
throw Error('Missing env!')
}
// Bail out if we're already compiling
if (state.compiling) {
// if compiling is ongoing return // TODO Inform user about it.
return;
return
}
// Set loading state to true
state.compiling = true;
state.compiling = true
state.logs = []
const file = state.files[activeId]
try {
@@ -32,29 +32,29 @@ export const compileCode = async (activeId: number) => {
let res: Response
try {
res = await fetch(process.env.NEXT_PUBLIC_COMPILE_API_ENDPOINT, {
method: "POST",
method: 'POST',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json'
},
body: JSON.stringify({
output: "wasm",
output: 'wasm',
compress: true,
strip: state.compileOptions.strip,
files: [
{
type: "c",
type: 'c',
options: state.compileOptions.optimizationLevel || '-O2',
name: file.name,
src: file.content,
},
],
}),
});
} catch (error) {
throw Error("Something went wrong, check your network connection and try again!")
src: file.content
}
const json = await res.json();
state.compiling = false;
]
})
})
} catch (error) {
throw Error('Something went wrong, check your network connection and try again!')
}
const json = await res.json()
state.compiling = false
if (!json.success) {
const errors = [json.message]
if (json.tasks && json.tasks.length > 0) {
@@ -62,65 +62,63 @@ export const compileCode = async (activeId: number) => {
if (!task.success) {
errors.push(task?.console)
}
});
})
}
throw errors
}
try {
// Decode base64 encoded wasm that is coming back from the endpoint
const bufferData = await decodeBinary(json.output);
const bufferData = await decodeBinary(json.output)
// Import wabt from and create human readable version of wasm file and
// put it into state
const ww = (await import('wabt')).default()
const myModule = ww.readWasm(new Uint8Array(bufferData), {
readDebugNames: true,
});
myModule.applyNames();
readDebugNames: true
})
myModule.applyNames()
const wast = myModule.toText({ foldExprs: false, inlineExport: false });
const wast = myModule.toText({ foldExprs: false, inlineExport: false })
file.compiledContent = ref(bufferData);
file.lastCompiled = new Date();
file.compiledContent = ref(bufferData)
file.lastCompiled = new Date()
file.compiledValueSnapshot = file.content
file.compiledWatContent = wast;
file.compiledWatContent = wast
} catch (error) {
throw Error("Invalid compilation result produced, check your code for errors and try again!")
throw Error('Invalid compilation result produced, check your code for errors and try again!')
}
toast.success("Compiled successfully!", { position: "bottom-center" });
toast.success('Compiled successfully!', { position: 'bottom-center' })
state.logs.push({
type: "success",
type: 'success',
message: `File ${state.files?.[activeId]?.name} compiled successfully. Ready to deploy.`,
link: Router.asPath.replace("develop", "deploy"),
linkText: "Go to deploy",
});
link: Router.asPath.replace('develop', 'deploy'),
linkText: 'Go to deploy'
})
} catch (err) {
console.log(err);
console.log(err)
if (err instanceof Array && typeof err[0] === 'string') {
err.forEach(message => {
state.logs.push({
type: "error",
message,
});
type: 'error',
message
})
})
} else if (err instanceof Error) {
state.logs.push({
type: 'error',
message: err.message
})
} else {
state.logs.push({
type: 'error',
message: 'Something went wrong, come back later!'
})
}
else if (err instanceof Error) {
state.logs.push({
type: "error",
message: err.message,
});
}
else {
state.logs.push({
type: "error",
message: "Something went wrong, come back later!",
});
}
state.compiling = false;
toast.error(`Error occurred while compiling!`, { position: "bottom-center" });
state.compiling = false
toast.error(`Error occurred while compiling!`, { position: 'bottom-center' })
file.containsErrors = true
}
};
}

View File

@@ -1,24 +1,28 @@
import state, { IFile } from '../index';
import state, { IFile } from '../index'
const languageMapping = {
'ts': 'typescript',
'js': 'javascript',
'md': 'markdown',
'c': 'c',
'h': 'c',
'other': ''
ts: 'typescript',
js: 'javascript',
md: 'markdown',
c: 'c',
h: 'c',
other: ''
} /* Initializes empty file to global state */
export const createNewFile = (name: string) => {
const tempName = name.split('.');
const fileExt = tempName[tempName.length - 1] || 'other';
const emptyFile: IFile = { name, language: languageMapping[fileExt as 'ts' | 'js' | 'md' | 'c' | 'h' | 'other'], content: "" };
state.files.push(emptyFile);
state.active = state.files.length - 1;
};
const tempName = name.split('.')
const fileExt = tempName[tempName.length - 1] || 'other'
const emptyFile: IFile = {
name,
language: languageMapping[fileExt as 'ts' | 'js' | 'md' | 'c' | 'h' | 'other'],
content: ''
}
state.files.push(emptyFile)
state.active = state.files.length - 1
}
export const renameFile = (oldName: string, nwName: string) => {
const file = state.files.find(file => file.name === oldName)
if (!file) throw Error(`No file exists with name ${oldName}`)
file.name = nwName
};
}

View File

@@ -1,24 +1,24 @@
import state, { transactionsState } from '..';
import state, { transactionsState } from '..'
export const deleteAccount = (addr?: string) => {
if (!addr) return;
const index = state.accounts.findIndex(acc => acc.address === addr);
if (index === -1) return;
state.accounts.splice(index, 1);
if (!addr) return
const index = state.accounts.findIndex(acc => acc.address === addr)
if (index === -1) return
state.accounts.splice(index, 1)
// update selected accounts
transactionsState.transactions
.filter(t => t.state.selectedAccount?.value === addr)
.forEach(t => {
const acc = t.state.selectedAccount;
if (!acc) return;
acc.label = acc.value;
});
const acc = t.state.selectedAccount
if (!acc) return
acc.label = acc.value
})
transactionsState.transactions
.filter(t => t.state.selectedDestAccount?.value === addr)
.forEach(t => {
const acc = t.state.selectedDestAccount;
if (!acc) return;
acc.label = acc.value;
});
};
const acc = t.state.selectedDestAccount
if (!acc) return
acc.label = acc.value
})
}

View File

@@ -1,53 +1,51 @@
import { derive, sign } from "xrpl-accountlib";
import toast from "react-hot-toast";
import { derive, sign } from 'xrpl-accountlib'
import toast from 'react-hot-toast'
import state, { IAccount } from "../index";
import calculateHookOn, { TTS } from "../../utils/hookOnCalculator";
import { Link } from "../../components";
import { ref } from "valtio";
import estimateFee from "../../utils/estimateFee";
import { SetHookData } from '../../utils/setHook';
import state, { IAccount } from '../index'
import calculateHookOn, { TTS } from '../../utils/hookOnCalculator'
import { Link } from '../../components'
import { ref } from 'valtio'
import estimateFee from '../../utils/estimateFee'
import { SetHookData } from '../../utils/setHook'
export const sha256 = async (string: string) => {
const utf8 = new TextEncoder().encode(string);
const hashBuffer = await crypto.subtle.digest("SHA-256", utf8);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray
.map((bytes) => bytes.toString(16).padStart(2, "0"))
.join("");
return hashHex;
};
const utf8 = new TextEncoder().encode(string)
const hashBuffer = await crypto.subtle.digest('SHA-256', utf8)
const hashArray = Array.from(new Uint8Array(hashBuffer))
const hashHex = hashArray.map(bytes => bytes.toString(16).padStart(2, '0')).join('')
return hashHex
}
function toHex(str: string) {
var result = "";
var result = ''
for (var i = 0; i < str.length; i++) {
result += str.charCodeAt(i).toString(16);
result += str.charCodeAt(i).toString(16)
}
return result.toUpperCase();
return result.toUpperCase()
}
function arrayBufferToHex(arrayBuffer?: ArrayBuffer | null) {
if (!arrayBuffer) {
return "";
return ''
}
if (
typeof arrayBuffer !== "object" ||
typeof arrayBuffer !== 'object' ||
arrayBuffer === null ||
typeof arrayBuffer.byteLength !== "number"
typeof arrayBuffer.byteLength !== 'number'
) {
throw new TypeError("Expected input to be an ArrayBuffer");
throw new TypeError('Expected input to be an ArrayBuffer')
}
var view = new Uint8Array(arrayBuffer);
var result = "";
var value;
var view = new Uint8Array(arrayBuffer)
var result = ''
var value
for (var i = 0; i < view.length; i++) {
value = view[i].toString(16);
result += value.length === 1 ? "0" + value : value;
value = view[i].toString(16)
result += value.length === 1 ? '0' + value : value
}
return result;
return result
}
export const prepareDeployHookTx = async (
@@ -56,30 +54,29 @@ export const prepareDeployHookTx = async (
) => {
const activeFile = state.files[state.active]?.compiledContent
? state.files[state.active]
: state.files.filter((file) => file.compiledContent)[0];
: state.files.filter(file => file.compiledContent)[0]
if (!state.files || state.files.length === 0) {
return;
return
}
if (!activeFile?.compiledContent) {
return;
return
}
if (!state.client) {
return;
return
}
const HookNamespace = (await sha256(data.HookNamespace)).toUpperCase();
const hookOnValues: (keyof TTS)[] = data.Invoke.map((tt) => tt.value);
const { HookParameters } = data;
const HookNamespace = (await sha256(data.HookNamespace)).toUpperCase()
const hookOnValues: (keyof TTS)[] = data.Invoke.map(tt => tt.value)
const { HookParameters } = data
const filteredHookParameters = HookParameters.filter(
(hp) =>
hp.HookParameter.HookParameterName && hp.HookParameter.HookParameterValue
)?.map((aa) => ({
hp => hp.HookParameter.HookParameterName && hp.HookParameter.HookParameterValue
)?.map(aa => ({
HookParameter: {
HookParameterName: toHex(aa.HookParameter.HookParameterName || ""),
HookParameterValue: aa.HookParameter.HookParameterValue || "",
},
}));
HookParameterName: toHex(aa.HookParameter.HookParameterName || ''),
HookParameterValue: aa.HookParameter.HookParameterValue || ''
}
}))
// const filteredHookGrants = HookGrants.filter(hg => hg.HookGrant.Authorize || hg.HookGrant.HookHash).map(hg => {
// return {
// HookGrant: {
@@ -89,82 +86,74 @@ export const prepareDeployHookTx = async (
// }
// }
// });
if (typeof window !== "undefined") {
if (typeof window !== 'undefined') {
const tx = {
Account: account.address,
TransactionType: "SetHook",
TransactionType: 'SetHook',
Sequence: account.sequence,
Fee: data.Fee,
Hooks: [
{
Hook: {
CreateCode: arrayBufferToHex(
activeFile?.compiledContent
).toUpperCase(),
CreateCode: arrayBufferToHex(activeFile?.compiledContent).toUpperCase(),
HookOn: calculateHookOn(hookOnValues),
HookNamespace,
HookApiVersion: 0,
Flags: 1,
// ...(filteredHookGrants.length > 0 && { HookGrants: filteredHookGrants }),
...(filteredHookParameters.length > 0 && {
HookParameters: filteredHookParameters,
}),
},
},
],
};
return tx;
HookParameters: filteredHookParameters
})
}
};
}
]
}
return tx
}
}
/* deployHook function turns the wasm binary into
* hex string, signs the transaction and deploys it to
* Hooks testnet.
*/
export const deployHook = async (
account: IAccount & { name?: string },
data: SetHookData
) => {
if (typeof window !== "undefined") {
export const deployHook = async (account: IAccount & { name?: string }, data: SetHookData) => {
if (typeof window !== 'undefined') {
const activeFile = state.files[state.active]?.compiledContent
? state.files[state.active]
: state.files.filter((file) => file.compiledContent)[0];
state.deployValues[activeFile.name] = data;
const tx = await prepareDeployHookTx(account, data);
: state.files.filter(file => file.compiledContent)[0]
state.deployValues[activeFile.name] = data
const tx = await prepareDeployHookTx(account, data)
if (!tx) {
return;
return
}
if (!state.client) {
return;
return
}
const keypair = derive.familySeed(account.secret);
const keypair = derive.familySeed(account.secret)
const { signedTransaction } = sign(tx, keypair);
const currentAccount = state.accounts.find(
(acc) => acc.address === account.address
);
const { signedTransaction } = sign(tx, keypair)
const currentAccount = state.accounts.find(acc => acc.address === account.address)
if (currentAccount) {
currentAccount.isLoading = true;
currentAccount.isLoading = true
}
let submitRes;
let submitRes
try {
submitRes = await state.client?.send({
command: "submit",
tx_blob: signedTransaction,
});
command: 'submit',
tx_blob: signedTransaction
})
if (submitRes.engine_result === "tesSUCCESS") {
if (submitRes.engine_result === 'tesSUCCESS') {
state.deployLogs.push({
type: "success",
message: "Hook deployed successfully ✅",
});
type: 'success',
message: 'Hook deployed successfully ✅'
})
state.deployLogs.push({
type: "success",
type: 'success',
message: ref(
<>
[{submitRes.engine_result}] {submitRes.engine_result_message}{" "}
Transaction hash:{" "}
[{submitRes.engine_result}] {submitRes.engine_result_message} Transaction hash:{' '}
<Link
as="a"
href={`https://${process.env.NEXT_PUBLIC_EXPLORER_URL}/${submitRes.tx_json?.hash}`}
@@ -174,113 +163,110 @@ export const deployHook = async (
{submitRes.tx_json?.hash}
</Link>
</>
),
)
// message: `[${submitRes.engine_result}] ${submitRes.engine_result_message} Validated ledger index: ${submitRes.validated_ledger_index}`,
});
})
} else {
state.deployLogs.push({
type: "error",
type: 'error',
message: `[${submitRes.engine_result || submitRes.error}] ${
submitRes.engine_result_message || submitRes.error_exception
}`,
});
}`
})
}
} catch (err) {
console.log(err);
console.log(err)
state.deployLogs.push({
type: "error",
message: "Error occurred while deploying",
});
type: 'error',
message: 'Error occurred while deploying'
})
}
if (currentAccount) {
currentAccount.isLoading = false;
currentAccount.isLoading = false
}
return submitRes;
return submitRes
}
};
}
export const deleteHook = async (account: IAccount & { name?: string }) => {
if (!state.client) {
return;
return
}
const currentAccount = state.accounts.find(
(acc) => acc.address === account.address
);
const currentAccount = state.accounts.find(acc => acc.address === account.address)
if (currentAccount?.isLoading || !currentAccount?.hooks.length) {
return;
return
}
if (typeof window !== "undefined") {
if (typeof window !== 'undefined') {
const tx = {
Account: account.address,
TransactionType: "SetHook",
TransactionType: 'SetHook',
Sequence: account.sequence,
Fee: "100000",
Fee: '100000',
Hooks: [
{
Hook: {
CreateCode: "",
Flags: 1,
},
},
],
};
CreateCode: '',
Flags: 1
}
}
]
}
const keypair = derive.familySeed(account.secret);
const keypair = derive.familySeed(account.secret)
try {
// Update tx Fee value with network estimation
const res = await estimateFee(tx, account);
tx["Fee"] = res?.base_fee ? res?.base_fee : "1000";
const res = await estimateFee(tx, account)
tx['Fee'] = res?.base_fee ? res?.base_fee : '1000'
} catch (err) {
// use default value what you defined earlier
console.log(err);
console.log(err)
}
const { signedTransaction } = sign(tx, keypair);
const { signedTransaction } = sign(tx, keypair)
if (currentAccount) {
currentAccount.isLoading = true;
currentAccount.isLoading = true
}
let submitRes;
const toastId = toast.loading("Deleting hook...");
let submitRes
const toastId = toast.loading('Deleting hook...')
try {
submitRes = await state.client.send({
command: "submit",
tx_blob: signedTransaction,
});
command: 'submit',
tx_blob: signedTransaction
})
if (submitRes.engine_result === "tesSUCCESS") {
toast.success("Hook deleted successfully ✅", { id: toastId });
if (submitRes.engine_result === 'tesSUCCESS') {
toast.success('Hook deleted successfully ✅', { id: toastId })
state.deployLogs.push({
type: "success",
message: "Hook deleted successfully ✅",
});
type: 'success',
message: 'Hook deleted successfully ✅'
})
state.deployLogs.push({
type: "success",
message: `[${submitRes.engine_result}] ${submitRes.engine_result_message} Validated ledger index: ${submitRes.validated_ledger_index}`,
});
currentAccount.hooks = [];
type: 'success',
message: `[${submitRes.engine_result}] ${submitRes.engine_result_message} Validated ledger index: ${submitRes.validated_ledger_index}`
})
currentAccount.hooks = []
} else {
toast.error(
`${submitRes.engine_result_message || submitRes.error_exception}`,
{ id: toastId }
);
toast.error(`${submitRes.engine_result_message || submitRes.error_exception}`, {
id: toastId
})
state.deployLogs.push({
type: "error",
type: 'error',
message: `[${submitRes.engine_result || submitRes.error}] ${
submitRes.engine_result_message || submitRes.error_exception
}`,
});
}`
})
}
} catch (err) {
console.log(err);
toast.error("Error occurred while deleting hook", { id: toastId });
console.log(err)
toast.error('Error occurred while deleting hook', { id: toastId })
state.deployLogs.push({
type: "error",
message: "Error occurred while deleting hook",
});
type: 'error',
message: 'Error occurred while deleting hook'
})
}
if (currentAccount) {
currentAccount.isLoading = false;
currentAccount.isLoading = false
}
return submitRes;
return submitRes
}
};
}

View File

@@ -1,20 +1,22 @@
import { createZip } from '../../utils/zip';
import { guessZipFileName } from '../../utils/helpers';
import { createZip } from '../../utils/zip'
import { guessZipFileName } from '../../utils/helpers'
import state from '..'
import toast from 'react-hot-toast';
import toast from 'react-hot-toast'
export const downloadAsZip = async () => {
try {
state.zipLoading = true
// TODO do something about file/gist loading state
const files = state.files.map(({ name, content }) => ({ name, content }));
const wasmFiles = state.files.filter(i => i.compiledContent).map(({ name, compiledContent }) => ({ name: `${name}.wasm`, content: compiledContent }));
const zipped = await createZip([...files, ...wasmFiles]);
const zipFileName = guessZipFileName(files);
zipped.saveFile(zipFileName);
const files = state.files.map(({ name, content }) => ({ name, content }))
const wasmFiles = state.files
.filter(i => i.compiledContent)
.map(({ name, compiledContent }) => ({ name: `${name}.wasm`, content: compiledContent }))
const zipped = await createZip([...files, ...wasmFiles])
const zipFileName = guessZipFileName(files)
zipped.saveFile(zipFileName)
} catch (error) {
toast.error('Error occurred while creating zip file, try again later')
} finally {
state.zipLoading = false
}
};
}

View File

@@ -1,50 +1,57 @@
import { Octokit } from "@octokit/core";
import Router from "next/router";
import state from '../index';
import { templateFileIds } from '../constants';
import { Octokit } from '@octokit/core'
import Router from 'next/router'
import state from '../index'
import { templateFileIds } from '../constants'
const octokit = new Octokit();
const octokit = new Octokit()
/* Fetches Gist files from Githug Gists based on
* gistId and stores the content in global state
*/
export const fetchFiles = (gistId: string) => {
state.loading = true;
state.loading = true
if (gistId && !state.files.length) {
state.logs.push({
type: "log",
message: `Fetching Gist with id: ${gistId}`,
});
type: 'log',
message: `Fetching Gist with id: ${gistId}`
})
octokit
.request("GET /gists/{gist_id}", { gist_id: gistId })
.request('GET /gists/{gist_id}', { gist_id: gistId })
.then(async res => {
if (!Object.values(templateFileIds).map(v => v.id).includes(gistId)) {
if (
!Object.values(templateFileIds)
.map(v => v.id)
.includes(gistId)
) {
return res
}
// in case of templates, fetch header file(s) and append to res
try {
const resHeader = await fetch(`${process.env.NEXT_PUBLIC_COMPILE_API_BASE_URL}/api/header-files`);
const resHeader = await fetch(
`${process.env.NEXT_PUBLIC_COMPILE_API_BASE_URL}/api/header-files`
)
if (resHeader.ok) {
const resHeaderJson = await resHeader.json()
const headerFiles: Record<string, { filename: string; content: string; language: string }> = {};
const headerFiles: Record<
string,
{ filename: string; content: string; language: string }
> = {}
Object.entries(resHeaderJson).forEach(([key, value]) => {
const fname = `${key}.h`;
const fname = `${key}.h`
headerFiles[fname] = { filename: fname, content: value as string, language: 'C' }
})
const files = {
...res.data.files,
...headerFiles
};
res.data.files = files;
}
res.data.files = files
}
} catch (err) {
console.log(err)
}
return res;
return res
// If you want to load templates from GIST instad, uncomment the code below and comment the code above.
// return octokit.request("GET /gists/{gist_id}", { gist_id: templateFileIds.headers }).then(({ data: { files: headerFiles } }) => {
// const files = { ...res.data.files, ...headerFiles }
@@ -53,65 +60,65 @@ export const fetchFiles = (gistId: string) => {
// return res
// })
})
.then((res) => {
.then(res => {
if (res.data.files && Object.keys(res.data.files).length > 0) {
const files = Object.keys(res.data.files).map((filename) => ({
name: res.data.files?.[filename]?.filename || "untitled.c",
language: res.data.files?.[filename]?.language?.toLowerCase() || "",
content: res.data.files?.[filename]?.content || "",
}));
const files = Object.keys(res.data.files).map(filename => ({
name: res.data.files?.[filename]?.filename || 'untitled.c',
language: res.data.files?.[filename]?.language?.toLowerCase() || '',
content: res.data.files?.[filename]?.content || ''
}))
// Sort files so that the source files are first
// In case of other files leave the order as it its
files.sort((a, b) => {
const aBasename = a.name.split('.')?.[0];
const aCext = a.name?.toLowerCase().endsWith('.c');
const bBasename = b.name.split('.')?.[0];
const bCext = b.name?.toLowerCase().endsWith('.c');
const aBasename = a.name.split('.')?.[0]
const aCext = a.name?.toLowerCase().endsWith('.c')
const bBasename = b.name.split('.')?.[0]
const bCext = b.name?.toLowerCase().endsWith('.c')
// If a has c extension and b doesn't move a up
if (aCext && !bCext) {
return -1;
return -1
}
if (!aCext && bCext) {
return 1
}
// Otherwise fallback to default sorting based on basename
if (aBasename > bBasename) {
return 1;
return 1
}
if (bBasename > aBasename) {
return -1;
return -1
}
return 0;
return 0
})
state.loading = false;
state.loading = false
if (files.length > 0) {
state.logs.push({
type: "success",
message: "Fetched successfully ✅",
});
state.files = files;
state.gistId = gistId;
state.gistName = Object.keys(res.data.files)?.[0] || "untitled";
state.gistOwner = res.data.owner?.login;
return;
type: 'success',
message: 'Fetched successfully ✅'
})
state.files = files
state.gistId = gistId
state.gistName = Object.keys(res.data.files)?.[0] || 'untitled'
state.gistOwner = res.data.owner?.login
return
} else {
// Open main modal if now files
state.mainModalOpen = true;
state.mainModalOpen = true
}
return Router.push({ pathname: "/develop" });
return Router.push({ pathname: '/develop' })
}
state.loading = false;
state.loading = false
})
.catch((err) => {
.catch(err => {
// console.error(err)
state.loading = false;
state.loading = false
state.logs.push({
type: "error",
message: `Couldn't find Gist with id: ${gistId}`,
});
return;
});
return;
type: 'error',
message: `Couldn't find Gist with id: ${gistId}`
})
return
})
return
}
state.loading = false;
};
state.loading = false
}

View File

@@ -1,40 +1,40 @@
import toast from "react-hot-toast";
import { derive, XRPL_Account } from "xrpl-accountlib";
import toast from 'react-hot-toast'
import { derive, XRPL_Account } from 'xrpl-accountlib'
import state from '../index';
import { names } from './addFaucetAccount';
import state from '../index'
import { names } from './addFaucetAccount'
// Adds test account to global state with secret key
export const importAccount = (secret: string, name?: string) => {
if (!secret) {
return toast.error("You need to add secret!");
return toast.error('You need to add secret!')
}
if (state.accounts.find((acc) => acc.secret === secret)) {
return toast.error("Account already added!");
if (state.accounts.find(acc => acc.secret === secret)) {
return toast.error('Account already added!')
}
let account: XRPL_Account | null = null;
let account: XRPL_Account | null = null
try {
account = derive.familySeed(secret);
account = derive.familySeed(secret)
} catch (err: any) {
if (err?.message) {
toast.error(err.message)
} else {
toast.error('Error occurred while importing account')
}
return;
return
}
if (!account || !account.secret.familySeed) {
return toast.error(`Couldn't create account!`);
return toast.error(`Couldn't create account!`)
}
state.accounts.push({
name: name || names[state.accounts.length],
address: account.address || "",
secret: account.secret.familySeed || "",
xrp: "0",
address: account.address || '',
secret: account.secret.familySeed || '',
xrp: '0',
sequence: 1,
hooks: [],
isLoading: false,
version: '2'
});
return toast.success("Account imported successfully!");
};
})
return toast.success('Account imported successfully!')
}

View File

@@ -1,14 +1,14 @@
import { addFaucetAccount } from "./addFaucetAccount";
import { compileCode } from "./compileCode";
import { createNewFile } from "./createNewFile";
import { deployHook } from "./deployHook";
import { fetchFiles } from "./fetchFiles";
import { importAccount } from "./importAccount";
import { saveFile } from "./saveFile";
import { syncToGist } from "./syncToGist";
import { updateEditorSettings } from "./updateEditorSettings";
import { downloadAsZip } from "./downloadAsZip";
import { sendTransaction } from "./sendTransaction";
import { addFaucetAccount } from './addFaucetAccount'
import { compileCode } from './compileCode'
import { createNewFile } from './createNewFile'
import { deployHook } from './deployHook'
import { fetchFiles } from './fetchFiles'
import { importAccount } from './importAccount'
import { saveFile } from './saveFile'
import { syncToGist } from './syncToGist'
import { updateEditorSettings } from './updateEditorSettings'
import { downloadAsZip } from './downloadAsZip'
import { sendTransaction } from './sendTransaction'
export {
addFaucetAccount,
@@ -22,4 +22,4 @@ export {
updateEditorSettings,
downloadAsZip,
sendTransaction
};
}

View File

@@ -1,5 +1,5 @@
import { snapshot } from "valtio"
import state from ".."
import { snapshot } from 'valtio'
import state from '..'
export type SplitSize = number[]
@@ -12,4 +12,3 @@ export const getSplit = (splitId: string): SplitSize | null => {
const split = splits[splitId]
return split ? split : null
}

View File

@@ -1,28 +1,28 @@
import toast from "react-hot-toast";
import state from '../index';
import toast from 'react-hot-toast'
import state from '../index'
// Saves the current editor content to global state
export const saveFile = (showToast: boolean = true, activeId?: number) => {
const editorModels = state.editorCtx?.getModels();
const sought = '/' + state.files[state.active].name;
const currentModel = editorModels?.find((editorModel) => {
return editorModel.uri.path.endsWith(sought);
});
const editorModels = state.editorCtx?.getModels()
const sought = '/' + state.files[state.active].name
const currentModel = editorModels?.find(editorModel => {
return editorModel.uri.path.endsWith(sought)
})
const file = state.files[activeId || state.active]
if (state.files.length > 0) {
file.content = currentModel?.getValue() || "";
file.content = currentModel?.getValue() || ''
}
if (showToast) {
toast.success("Saved successfully", { position: "bottom-center" });
toast.success('Saved successfully', { position: 'bottom-center' })
}
};
}
export const saveAllFiles = () => {
const editorModels = state.editorCtx?.getModels();
const editorModels = state.editorCtx?.getModels()
state.files.forEach(file => {
const currentModel = editorModels?.find(model => model.uri.path.endsWith('/' + file.name))
if (currentModel) {
file.content = currentModel?.getValue() || '';
file.content = currentModel?.getValue() || ''
}
})
}

View File

@@ -1,12 +1,12 @@
import { derive, sign } from "xrpl-accountlib";
import { derive, sign } from 'xrpl-accountlib'
import state from '..'
import type { IAccount } from "..";
import type { IAccount } from '..'
interface TransactionOptions {
TransactionType: string,
Account?: string,
Fee?: string,
TransactionType: string
Account?: string
Fee?: string
Destination?: string
[index: string]: any
}
@@ -14,44 +14,53 @@ interface OtherOptions {
logPrefix?: string
}
export const sendTransaction = async (account: IAccount, txOptions: TransactionOptions, options?: OtherOptions) => {
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 { Fee = '1000', ...opts } = txOptions
const tx: TransactionOptions = {
Account: account.address,
Sequence: account.sequence,
Fee, // TODO auto-fillable default
...opts
};
}
const { logPrefix = '' } = options || {}
try {
const signedAccount = derive.familySeed(account.secret);
const { signedTransaction } = sign(tx, signedAccount);
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") {
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}`,
});
type: 'error',
message: `${logPrefix}[${response.error || response.engine_result}] ${
response.error_exception || response.engine_result_message
}`
})
}
const currAcc = state.accounts.find(acc => acc.address === account.address);
const currAcc = state.accounts.find(acc => acc.address === account.address)
if (currAcc && response.account_sequence_next) {
currAcc.sequence = response.account_sequence_next;
currAcc.sequence = response.account_sequence_next
}
} catch (err) {
console.error(err);
console.error(err)
state.transactionLogs.push({
type: "error",
message: err instanceof Error ? `${logPrefix}Error: ${err.message}` : `${logPrefix}Something went wrong, try again later`,
});
type: 'error',
message:
err instanceof Error
? `${logPrefix}Error: ${err.message}`
: `${logPrefix}Something went wrong, try again later`
})
}
};
}

View File

@@ -1,10 +1,14 @@
import { ref } from 'valtio';
import { AlertState, alertState } from "../../components/AlertDialog";
import { ref } from 'valtio'
import { AlertState, alertState } from '../../components/AlertDialog'
export const showAlert = (title: string, opts: Omit<Partial<AlertState>, 'title' | 'isOpen'> = {}) => {
export const showAlert = (
title: string,
opts: Omit<Partial<AlertState>, 'title' | 'isOpen'> = {}
) => {
const { body: _body, confirmPrefix: _confirmPrefix, ...rest } = opts
const body = (_body && typeof _body === 'object') ? ref(_body) : _body
const confirmPrefix = (_confirmPrefix && typeof _confirmPrefix === 'object') ? ref(_confirmPrefix) : _confirmPrefix
const body = _body && typeof _body === 'object' ? ref(_body) : _body
const confirmPrefix =
_confirmPrefix && typeof _confirmPrefix === 'object' ? ref(_confirmPrefix) : _confirmPrefix
const nwState: AlertState = {
isOpen: true,
@@ -15,9 +19,9 @@ export const showAlert = (title: string, opts: Omit<Partial<AlertState>, 'title'
confirmText: undefined,
onCancel: undefined,
onConfirm: undefined,
...rest,
...rest
}
Object.entries(nwState).forEach(([key, value]) => {
(alertState as any)[key] = value
;(alertState as any)[key] = value
})
}

View File

@@ -1,104 +1,97 @@
import type { Session } from "next-auth";
import toast from "react-hot-toast";
import { Octokit } from "@octokit/core";
import Router from "next/router";
import type { Session } from 'next-auth'
import toast from 'react-hot-toast'
import { Octokit } from '@octokit/core'
import Router from 'next/router'
import state from '../index';
import { saveAllFiles } from "./saveFile";
import state from '../index'
import { saveAllFiles } from './saveFile'
const octokit = new Octokit();
const octokit = new Octokit()
// Syncs the current files from the state to GitHub Gists.
export const syncToGist = async (
session?: Session | null,
createNewGist?: boolean
) => {
saveAllFiles();
let files: Record<string, { filename: string; content: string }> = {};
state.gistLoading = true;
export const syncToGist = async (session?: Session | null, createNewGist?: boolean) => {
saveAllFiles()
let files: Record<string, { filename: string; content: string }> = {}
state.gistLoading = true
if (!session || !session.user) {
state.gistLoading = false;
return toast.error("You need to be logged in!");
state.gistLoading = false
return toast.error('You need to be logged in!')
}
const toastId = toast.loading("Pushing to Gist");
const toastId = toast.loading('Pushing to Gist')
if (!state.files || !state.files.length) {
state.gistLoading = false;
state.gistLoading = false
return toast.error(`You need to create some files we can push to gist`, {
id: toastId,
});
id: toastId
})
}
if (
state.gistId &&
session?.user.username === state.gistOwner &&
!createNewGist
) {
if (state.gistId && session?.user.username === state.gistOwner && !createNewGist) {
// You can only remove files from Gist by updating file with empty contents
// So we need to fetch existing files and compare those to local state
// and then send empty content if we don't have matching files anymore
// on local state
const currentFilesRes = await octokit.request("GET /gists/{gist_id}", {
gist_id: state.gistId,
});
const currentFilesRes = await octokit.request('GET /gists/{gist_id}', {
gist_id: state.gistId
})
if (currentFilesRes.data.files) {
Object.keys(currentFilesRes?.data?.files).forEach((filename) => {
files[`${filename}`] = { filename, content: "" };
});
Object.keys(currentFilesRes?.data?.files).forEach(filename => {
files[`${filename}`] = { filename, content: '' }
})
}
state.files.forEach((file) => {
files[`${file.name}`] = { filename: file.name, content: file.content };
});
state.files.forEach(file => {
files[`${file.name}`] = { filename: file.name, content: file.content }
})
// Update existing Gist
octokit
.request("PATCH /gists/{gist_id}", {
.request('PATCH /gists/{gist_id}', {
gist_id: state.gistId,
files,
headers: {
authorization: `token ${session?.accessToken || ""}`,
},
authorization: `token ${session?.accessToken || ''}`
}
})
.then((res) => {
state.gistLoading = false;
return toast.success("Updated to gist successfully!", { id: toastId });
.then(res => {
state.gistLoading = false
return toast.success('Updated to gist successfully!', { id: toastId })
})
.catch((err) => {
console.log(err);
state.gistLoading = false;
.catch(err => {
console.log(err)
state.gistLoading = false
return toast.error(`Could not update Gist, try again later!`, {
id: toastId,
});
});
id: toastId
})
})
} else {
// Not Gist of the current user or it isn't Gist yet
state.files.forEach((file) => {
files[`${file.name}`] = { filename: file.name, content: file.content };
});
state.files.forEach(file => {
files[`${file.name}`] = { filename: file.name, content: file.content }
})
octokit
.request("POST /gists", {
.request('POST /gists', {
files,
public: true,
headers: {
authorization: `token ${session?.accessToken || ""}`,
},
authorization: `token ${session?.accessToken || ''}`
}
})
.then((res) => {
state.gistLoading = false;
state.gistOwner = res.data.owner?.login;
state.gistId = res.data.id;
.then(res => {
state.gistLoading = false
state.gistOwner = res.data.owner?.login
state.gistId = res.data.id
state.gistName = Array.isArray(res.data.files)
? Object.keys(res.data?.files)?.[0]
: "Untitled";
Router.push({ pathname: `/develop/${res.data.id}` });
return toast.success("Created new gist successfully!", { id: toastId });
: 'Untitled'
Router.push({ pathname: `/develop/${res.data.id}` })
return toast.success('Created new gist successfully!', { id: toastId })
})
.catch((err) => {
console.log(err);
state.gistLoading = false;
.catch(err => {
console.log(err)
state.gistLoading = false
return toast.error(`Could not create Gist, try again later!`, {
id: toastId,
});
});
id: toastId
})
})
}
};
}
export default syncToGist;
export default syncToGist

View File

@@ -1,14 +1,12 @@
import state, { IState } from '../index';
import state, { IState } from '../index'
// Updates editor settings and stores them
// in global state
export const updateEditorSettings = (
editorSettings: IState["editorSettings"]
) => {
state.editorCtx?.getModels().forEach((model) => {
export const updateEditorSettings = (editorSettings: IState['editorSettings']) => {
state.editorCtx?.getModels().forEach(model => {
model.updateOptions({
...editorSettings,
});
});
return (state.editorSettings = editorSettings);
};
...editorSettings
})
})
return (state.editorSettings = editorSettings)
}

View File

@@ -1,41 +1,41 @@
import Carbon from "../../components/icons/Carbon";
import Firewall from "../../components/icons/Firewall";
import Notary from "../../components/icons/Notary";
import Peggy from "../../components/icons/Peggy";
import Starter from "../../components/icons/Starter";
import Carbon from '../../components/icons/Carbon'
import Firewall from '../../components/icons/Firewall'
import Notary from '../../components/icons/Notary'
import Peggy from '../../components/icons/Peggy'
import Starter from '../../components/icons/Starter'
export const templateFileIds = {
'starter': {
starter: {
id: '9106f1fe60482d90475bfe8f1315affe',
name: 'Starter',
description: 'Just a basic starter with essential imports, just accepts any transaction coming through',
description:
'Just a basic starter with essential imports, just accepts any transaction coming through',
icon: Starter
},
'firewall': {
firewall: {
id: '1cc30f39c8a0b9c55b88c312669ca45e', // Forked
name: 'Firewall',
description: 'This Hook essentially checks a blacklist of accounts',
icon: Firewall
},
'notary': {
notary: {
id: '87b6f5a8c2f5038fb0f20b8b510efa10', // Forked
name: 'Notary',
description: 'Collecting signatures for multi-sign transactions',
icon: Notary
},
'carbon': {
carbon: {
id: '5941c19dce3e147948f564e224553c02',
name: 'Carbon',
description: 'Send a percentage of sum to an address',
icon: Carbon
},
'peggy': {
peggy: {
id: '049784a83fa068faf7912f663f7b6471', // Forked
name: 'Peggy',
description: 'An oracle based stable coin hook',
icon: Peggy
},
}
}
export const apiHeaderFiles = ['hookapi.h', 'sfcodes.h', 'macro.h', 'extern.h', 'error.h'];
export const apiHeaderFiles = ['hookapi.h', 'sfcodes.h', 'macro.h', 'extern.h', 'error.h']

View File

@@ -1,92 +1,92 @@
import type monaco from "monaco-editor";
import { proxy, ref, subscribe } from "valtio";
import { devtools, subscribeKey } from 'valtio/utils';
import { XrplClient } from "xrpl-client";
import { SplitSize } from "./actions/persistSplits";
import type monaco from 'monaco-editor'
import { proxy, ref, subscribe } from 'valtio'
import { devtools, subscribeKey } from 'valtio/utils'
import { XrplClient } from 'xrpl-client'
import { SplitSize } from './actions/persistSplits'
declare module "valtio" {
function useSnapshot<T extends object>(p: T): T;
function snapshot<T extends object>(p: T): T;
declare module 'valtio' {
function useSnapshot<T extends object>(p: T): T
function snapshot<T extends object>(p: T): T
}
export interface IFile {
name: string;
language: string;
content: string;
name: string
language: string
content: string
compiledValueSnapshot?: string
compiledContent?: ArrayBuffer | null;
compiledWatContent?: string | null;
compiledContent?: ArrayBuffer | null
compiledWatContent?: string | null
lastCompiled?: Date
containsErrors?: boolean
}
export interface FaucetAccountRes {
address: string;
secret: string;
xrp: number;
hash: string;
code: string;
address: string
secret: string
xrp: number
hash: string
code: string
}
export interface IAccount {
name: string;
address: string;
secret: string;
xrp: string;
sequence: number;
hooks: string[];
isLoading: boolean;
version?: string;
name: string
address: string
secret: string
xrp: string
sequence: number
hooks: string[]
isLoading: boolean
version?: string
error?: {
message: string;
code: string;
} | null;
message: string
code: string
} | null
}
export interface ILog {
type: "error" | "warning" | "log" | "success";
message: string | JSX.Element;
key?: string;
jsonData?: any,
timestring?: string;
link?: string;
linkText?: string;
type: 'error' | 'warning' | 'log' | 'success'
message: string | JSX.Element
key?: string
jsonData?: any
timestring?: string
link?: string
linkText?: string
defaultCollapsed?: boolean
}
export type DeployValue = Record<IFile['name'], any>;
export type DeployValue = Record<IFile['name'], any>
export interface IState {
files: IFile[];
gistId?: string | null;
gistOwner?: string | null;
gistName?: string | null;
active: number;
activeWat: number;
loading: boolean;
gistLoading: boolean;
zipLoading: boolean;
compiling: boolean;
logs: ILog[];
deployLogs: ILog[];
transactionLogs: ILog[];
scriptLogs: ILog[];
editorCtx?: typeof monaco.editor;
files: IFile[]
gistId?: string | null
gistOwner?: string | null
gistName?: string | null
active: number
activeWat: number
loading: boolean
gistLoading: boolean
zipLoading: boolean
compiling: boolean
logs: ILog[]
deployLogs: ILog[]
transactionLogs: ILog[]
scriptLogs: ILog[]
editorCtx?: typeof monaco.editor
editorSettings: {
tabSize: number;
};
tabSize: number
}
splits: {
[id: string]: SplitSize
};
client: XrplClient | null;
clientStatus: "offline" | "online";
mainModalOpen: boolean;
mainModalShowed: boolean;
accounts: IAccount[];
}
client: XrplClient | null
clientStatus: 'offline' | 'online'
mainModalOpen: boolean
mainModalShowed: boolean
accounts: IAccount[]
compileOptions: {
optimizationLevel: '-O0' | '-O1' | '-O2' | '-O3' | '-O4' | '-Os';
optimizationLevel: '-O0' | '-O1' | '-O2' | '-O3' | '-O4' | '-Os'
strip: boolean
},
}
deployValues: DeployValue
}
@@ -110,11 +110,11 @@ let initialState: IState = {
gistLoading: false,
zipLoading: false,
editorSettings: {
tabSize: 2,
tabSize: 2
},
splits: {},
client: null,
clientStatus: "offline" as "offline",
clientStatus: 'offline' as 'offline',
mainModalOpen: false,
mainModalShowed: false,
accounts: [],
@@ -123,23 +123,23 @@ let initialState: IState = {
strip: true
},
deployValues: {}
};
}
let localStorageAccounts: string | null = null;
let initialAccounts: IAccount[] = [];
let localStorageAccounts: string | null = null
let initialAccounts: IAccount[] = []
// TODO: What exactly should we store in localStorage? editorSettings, splits, accounts?
// Check if there's a persited accounts in localStorage
if (typeof window !== "undefined") {
if (typeof window !== 'undefined') {
try {
localStorageAccounts = localStorage.getItem("hooksIdeAccounts");
localStorageAccounts = localStorage.getItem('hooksIdeAccounts')
} catch (err) {
console.log(`localStorage state broken`);
localStorage.removeItem("hooksIdeAccounts");
console.log(`localStorage state broken`)
localStorage.removeItem('hooksIdeAccounts')
}
if (localStorageAccounts) {
initialAccounts = JSON.parse(localStorageAccounts);
initialAccounts = JSON.parse(localStorageAccounts)
}
// filter out old accounts (they do not have version property at all)
// initialAccounts = initialAccounts.filter(acc => acc.version === '2');
@@ -149,36 +149,35 @@ if (typeof window !== "undefined") {
const state = proxy<IState>({
...initialState,
accounts: initialAccounts.length > 0 ? initialAccounts : [],
logs: [],
});
logs: []
})
// Initialize socket connection
const client = new XrplClient(`wss://${process.env.NEXT_PUBLIC_TESTNET_URL}`);
const client = new XrplClient(`wss://${process.env.NEXT_PUBLIC_TESTNET_URL}`)
client.on("online", () => {
state.client = ref(client);
state.clientStatus = "online";
});
client.on('online', () => {
state.client = ref(client)
state.clientStatus = 'online'
})
client.on("offline", () => {
state.clientStatus = "offline";
});
client.on('offline', () => {
state.clientStatus = 'offline'
})
if (process.env.NODE_ENV !== "production") {
devtools(state, "Files State");
if (process.env.NODE_ENV !== 'production') {
devtools(state, 'Files State')
}
if (typeof window !== "undefined") {
if (typeof window !== 'undefined') {
subscribe(state.accounts, () => {
const { accounts } = state;
const { accounts } = state
const accountsNoLoading = accounts.map(acc => ({ ...acc, isLoading: false }))
localStorage.setItem("hooksIdeAccounts", JSON.stringify(accountsNoLoading));
});
localStorage.setItem('hooksIdeAccounts', JSON.stringify(accountsNoLoading))
})
const updateActiveWat = () => {
const filename = state.files[state.active]?.name
const compiledFiles = state.files.filter(
file => file.compiledContent)
const compiledFiles = state.files.filter(file => file.compiledContent)
const idx = compiledFiles.findIndex(file => file.name === filename)
if (idx !== -1) state.activeWat = idx

View File

@@ -1,32 +1,31 @@
import { proxy } from 'valtio';
import { deepEqual } from '../utils/object';
import transactionsData from "../content/transactions.json";
import state from '.';
import { showAlert } from "../state/actions/showAlert";
import { parseJSON } from '../utils/json';
import { proxy } from 'valtio'
import { deepEqual } from '../utils/object'
import transactionsData from '../content/transactions.json'
import state from '.'
import { showAlert } from '../state/actions/showAlert'
import { parseJSON } from '../utils/json'
export type SelectOption = {
value: string;
label: string;
};
value: string
label: string
}
export interface TransactionState {
selectedTransaction: SelectOption | null;
selectedAccount: SelectOption | null;
selectedDestAccount: SelectOption | null;
txIsLoading: boolean;
txIsDisabled: boolean;
txFields: TxFields;
viewType: 'json' | 'ui',
editorValue?: string,
selectedTransaction: SelectOption | null
selectedAccount: SelectOption | null
selectedDestAccount: SelectOption | null
txIsLoading: boolean
txIsDisabled: boolean
txFields: TxFields
viewType: 'json' | 'ui'
editorValue?: string
estimatedFee?: string
}
export type TxFields = Omit<
Partial<typeof transactionsData[0]>,
"Account" | "Sequence" | "TransactionType"
>;
'Account' | 'Sequence' | 'TransactionType'
>
export const defaultTransaction: TransactionState = {
selectedTransaction: null,
@@ -36,22 +35,22 @@ export const defaultTransaction: TransactionState = {
txIsDisabled: false,
txFields: {},
viewType: 'ui'
};
}
export const transactionsState = proxy({
transactions: [
{
header: "test1.json",
state: { ...defaultTransaction },
},
header: 'test1.json',
state: { ...defaultTransaction }
}
],
activeHeader: "test1.json"
});
activeHeader: 'test1.json'
})
export const renameTxState = (oldName: string, nwName: string) => {
const tx = transactionsState.transactions.find(tx => tx.header === oldName);
const tx = transactionsState.transactions.find(tx => tx.header === oldName)
if (!tx) throw Error(`No transaction state exists with given header name ${oldName}`);
if (!tx) throw Error(`No transaction state exists with given header name ${oldName}`)
tx.header = nwName
}
@@ -67,31 +66,31 @@ export const modifyTxState = (
partialTx?: Partial<TransactionState>,
opts: { replaceState?: boolean } = {}
) => {
const tx = transactionsState.transactions.find(tx => tx.header === header);
const tx = transactionsState.transactions.find(tx => tx.header === header)
if (partialTx === undefined) {
transactionsState.transactions = transactionsState.transactions.filter(
tx => tx.header !== header
);
return;
)
return
}
if (!tx) {
const state = {
...defaultTransaction,
...partialTx,
...partialTx
}
transactionsState.transactions.push({
header,
state,
});
return state;
state
})
return state
}
if (opts.replaceState) {
const repTx: TransactionState = {
...defaultTransaction,
...partialTx,
...partialTx
}
tx.state = repTx
return repTx
@@ -99,38 +98,39 @@ export const modifyTxState = (
Object.keys(partialTx).forEach(k => {
// Typescript mess here, but is definitely safe!
const s = tx.state as any;
const p = partialTx as any; // ? Make copy
if (!deepEqual(s[k], p[k])) s[k] = p[k];
});
const s = tx.state as any
const p = partialTx as any // ? Make copy
if (!deepEqual(s[k], p[k])) s[k] = p[k]
})
return tx.state
};
}
// state to tx options
export const prepareTransaction = (data: any) => {
let options = { ...data };
let options = { ...data }
(Object.keys(options)).forEach(field => {
let _value = options[field];
Object.keys(options).forEach(field => {
let _value = options[field]
// convert xrp
if (_value && typeof _value === "object" && _value.$type === "xrp") {
if (_value && typeof _value === 'object' && _value.$type === 'xrp') {
if (+_value.$value) {
options[field] = (+_value.$value * 1000000 + "") as any;
options[field] = (+_value.$value * 1000000 + '') as any
} else {
options[field] = undefined; // 👇 💀
options[field] = undefined // 👇 💀
}
}
// handle type: `json`
if (_value && typeof _value === "object" && _value.$type === "json") {
if (typeof _value.$value === "object") {
options[field] = _value.$value;
if (_value && typeof _value === 'object' && _value.$type === 'json') {
if (typeof _value.$value === 'object') {
options[field] = _value.$value
} else {
try {
options[field] = JSON.parse(_value.$value);
options[field] = JSON.parse(_value.$value)
} catch (error) {
const message = `Input error for json field '${field}': ${error instanceof Error ? error.message : ""
}`;
const message = `Input error for json field '${field}': ${
error instanceof Error ? error.message : ''
}`
console.error(message)
options[field] = _value.$value
}
@@ -139,114 +139,106 @@ export const prepareTransaction = (data: any) => {
// delete unnecessary fields
if (!options[field]) {
delete options[field];
delete options[field]
}
});
})
return options
}
// editor value to state
export const prepareState = (value: string, transactionType?: string) => {
const options = parseJSON(value);
const options = parseJSON(value)
if (!options) {
showAlert("Error!", {
body: "Cannot save editor with malformed transaction."
showAlert('Error!', {
body: 'Cannot save editor with malformed transaction.'
})
return
};
}
const { Account, TransactionType, Destination, ...rest } = options;
let tx: Partial<TransactionState> = {};
const { Account, TransactionType, Destination, ...rest } = options
let tx: Partial<TransactionState> = {}
const schema = getTxFields(transactionType)
if (Account) {
const acc = state.accounts.find(acc => acc.address === Account);
const acc = state.accounts.find(acc => acc.address === Account)
if (acc) {
tx.selectedAccount = {
label: acc.name,
value: acc.address,
};
value: acc.address
}
} else {
tx.selectedAccount = {
label: Account,
value: Account,
};
value: Account
}
}
} else {
tx.selectedAccount = null;
tx.selectedAccount = null
}
if (TransactionType) {
tx.selectedTransaction = {
label: TransactionType,
value: TransactionType,
};
value: TransactionType
}
} else {
tx.selectedTransaction = null;
tx.selectedTransaction = null
}
if (schema.Destination !== undefined) {
const dest = state.accounts.find(acc => acc.address === Destination);
const dest = state.accounts.find(acc => acc.address === Destination)
if (dest) {
tx.selectedDestAccount = {
label: dest.name,
value: dest.address,
};
value: dest.address
}
else if (Destination) {
} else if (Destination) {
tx.selectedDestAccount = {
label: Destination,
value: Destination,
};
value: Destination
}
else {
} else {
tx.selectedDestAccount = null
}
}
else if (Destination) {
} else if (Destination) {
rest.Destination = Destination
}
Object.keys(rest).forEach(field => {
const value = rest[field];
const value = rest[field]
const schemaVal = schema[field as keyof TxFields]
const isXrp = typeof value !== 'object' && schemaVal && typeof schemaVal === 'object' && schemaVal.$type === 'xrp'
const isXrp =
typeof value !== 'object' &&
schemaVal &&
typeof schemaVal === 'object' &&
schemaVal.$type === 'xrp'
if (isXrp) {
rest[field] = {
$type: "xrp",
$value: +value / 1000000, // ! maybe use bigint?
};
} else if (typeof value === "object") {
rest[field] = {
$type: "json",
$value: value,
};
$type: 'xrp',
$value: +value / 1000000 // ! maybe use bigint?
}
});
} else if (typeof value === 'object') {
rest[field] = {
$type: 'json',
$value: value
}
}
})
tx.txFields = rest;
tx.txFields = rest
return tx
}
export const getTxFields = (tt?: string) => {
const txFields: TxFields | undefined = transactionsData.find(
tx => tx.TransactionType === tt
);
const txFields: TxFields | undefined = transactionsData.find(tx => tx.TransactionType === tt)
if (!txFields) return {}
let _txFields = Object.keys(txFields)
.filter(
key => !["TransactionType", "Account", "Sequence"].includes(key)
)
.reduce<TxFields>(
(tf, key) => (
(tf[key as keyof TxFields] = (txFields as any)[key]), tf
),
{}
);
.filter(key => !['TransactionType', 'Account', 'Sequence'].includes(key))
.reduce<TxFields>((tf, key) => ((tf[key as keyof TxFields] = (txFields as any)[key]), tf), {})
return _txFields
}
@@ -254,7 +246,7 @@ export { transactionsData }
export const transactionsOptions = transactionsData.map(tx => ({
value: tx.TransactionType,
label: tx.TransactionType,
}));
label: tx.TransactionType
}))
export const defaultTransactionType = transactionsOptions.find(tt => tt.value === 'Payment')

View File

@@ -1,6 +1,6 @@
// 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,
@@ -24,19 +24,11 @@ import {
purpleDark,
greenDark,
red,
redDark,
} from "@radix-ui/colors";
redDark
} from '@radix-ui/colors'
export const {
styled,
css,
globalCss,
keyframes,
getCssText,
theme,
createTheme,
config,
} = createStitches({
export const { styled, css, globalCss, keyframes, getCssText, theme, createTheme, config } =
createStitches({
theme: {
colors: {
...gray,
@@ -50,309 +42,267 @@ export const {
...purple,
...green,
...red,
accent: "#9D2DFF",
background: "$gray1",
backgroundAlt: "$gray4",
backgroundOverlay: "$mauve2",
text: "$gray12",
textMuted: "$gray10",
primary: "$plum",
accent: '#9D2DFF',
background: '$gray1',
backgroundAlt: '$gray4',
backgroundOverlay: '$mauve2',
text: '$gray12',
textMuted: '$gray10',
primary: '$plum',
error: '$red9',
warning: '$amber11',
success: "$grass11",
white: "white",
black: "black",
deep: "rgb(244, 244, 244)",
success: '$grass11',
white: 'white',
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",
sm: "0.875rem",
md: "1rem",
lg: "1.125rem",
xl: "1.25rem",
"2xl": "1.5rem",
"3xl": "1.875rem",
"4xl": "2.25rem",
"5xl": "3rem",
"6xl": "3.75rem",
"7xl": "4.5rem",
"8xl": "6rem",
"9xl": "8rem",
default: "$md",
xs: '0.6875rem',
sm: '0.875rem',
md: '1rem',
lg: '1.125rem',
xl: '1.25rem',
'2xl': '1.5rem',
'3xl': '1.875rem',
'4xl': '2.25rem',
'5xl': '3rem',
'6xl': '3.75rem',
'7xl': '4.5rem',
'8xl': '6rem',
'9xl': '8rem',
default: '$md'
},
space: {
px: "1px",
0.5: "0.125rem",
1: "0.25rem",
1.5: "0.375rem",
2: "0.5rem",
2.5: "0.625rem",
3: "0.75rem",
3.5: "0.875rem",
4: "1rem",
5: "1.25rem",
6: "1.5rem",
7: "1.75rem",
8: "2rem",
9: "2.25rem",
10: "2.5rem",
12: "3rem",
14: "3.5rem",
16: "4rem",
20: "5rem",
24: "6rem",
28: "7rem",
32: "8rem",
36: "9rem",
40: "10rem",
44: "11rem",
48: "12rem",
52: "13rem",
56: "14rem",
60: "15rem",
64: "16rem",
72: "18rem",
80: "20rem",
96: "24rem",
widePlus: "2048px",
wide: "1536px",
layoutPlus: "1260px",
layout: "1024px",
copyUltra: "980px",
copyPlus: "768px",
copy: "680px",
narrowPlus: "600px",
narrow: "512px",
xs: "20rem",
sm: "24rem",
md: "28rem",
lg: "32rem",
xl: "36rem",
"2xl": "42rem",
"3xl": "48rem",
"4xl": "56rem",
"5xl": "64rem",
"6xl": "72rem",
"7xl": "80rem",
"8xl": "90rem",
px: '1px',
0.5: '0.125rem',
1: '0.25rem',
1.5: '0.375rem',
2: '0.5rem',
2.5: '0.625rem',
3: '0.75rem',
3.5: '0.875rem',
4: '1rem',
5: '1.25rem',
6: '1.5rem',
7: '1.75rem',
8: '2rem',
9: '2.25rem',
10: '2.5rem',
12: '3rem',
14: '3.5rem',
16: '4rem',
20: '5rem',
24: '6rem',
28: '7rem',
32: '8rem',
36: '9rem',
40: '10rem',
44: '11rem',
48: '12rem',
52: '13rem',
56: '14rem',
60: '15rem',
64: '16rem',
72: '18rem',
80: '20rem',
96: '24rem',
widePlus: '2048px',
wide: '1536px',
layoutPlus: '1260px',
layout: '1024px',
copyUltra: '980px',
copyPlus: '768px',
copy: '680px',
narrowPlus: '600px',
narrow: '512px',
xs: '20rem',
sm: '24rem',
md: '28rem',
lg: '32rem',
xl: '36rem',
'2xl': '42rem',
'3xl': '48rem',
'4xl': '56rem',
'5xl': '64rem',
'6xl': '72rem',
'7xl': '80rem',
'8xl': '90rem'
},
sizes: {
px: "1px",
0.5: "0.125rem",
1: "0.25rem",
1.5: "0.375rem",
2: "0.5rem",
2.5: "0.625rem",
3: "0.75rem",
3.5: "0.875rem",
4: "1rem",
5: "1.25rem",
6: "1.5rem",
7: "1.75rem",
8: "2rem",
9: "2.25rem",
10: "2.5rem",
12: "3rem",
14: "3.5rem",
16: "4rem",
20: "5rem",
24: "6rem",
28: "7rem",
32: "8rem",
36: "9rem",
40: "10rem",
44: "11rem",
48: "12rem",
52: "13rem",
56: "14rem",
60: "15rem",
64: "16rem",
72: "18rem",
80: "20rem",
96: "24rem",
max: "max-content",
min: "min-content",
full: "100%",
"3xs": "14rem",
"2xs": "16rem",
xs: "20rem",
sm: "24rem",
md: "28rem",
lg: "32rem",
xl: "36rem",
"2xl": "42rem",
"3xl": "48rem",
"4xl": "56rem",
"5xl": "64rem",
"6xl": "72rem",
"7xl": "80rem",
"8xl": "90rem",
px: '1px',
0.5: '0.125rem',
1: '0.25rem',
1.5: '0.375rem',
2: '0.5rem',
2.5: '0.625rem',
3: '0.75rem',
3.5: '0.875rem',
4: '1rem',
5: '1.25rem',
6: '1.5rem',
7: '1.75rem',
8: '2rem',
9: '2.25rem',
10: '2.5rem',
12: '3rem',
14: '3.5rem',
16: '4rem',
20: '5rem',
24: '6rem',
28: '7rem',
32: '8rem',
36: '9rem',
40: '10rem',
44: '11rem',
48: '12rem',
52: '13rem',
56: '14rem',
60: '15rem',
64: '16rem',
72: '18rem',
80: '20rem',
96: '24rem',
max: 'max-content',
min: 'min-content',
full: '100%',
'3xs': '14rem',
'2xs': '16rem',
xs: '20rem',
sm: '24rem',
md: '28rem',
lg: '32rem',
xl: '36rem',
'2xl': '42rem',
'3xl': '48rem',
'4xl': '56rem',
'5xl': '64rem',
'6xl': '72rem',
'7xl': '80rem',
'8xl': '90rem'
},
radii: {
none: "0",
sm: "0.2rem",
base: "0.25rem",
md: "0.375rem",
lg: "0.5rem",
xl: "0.75rem",
"2xl": "1rem",
"3xl": "1.5rem",
full: "9999px",
none: '0',
sm: '0.2rem',
base: '0.25rem',
md: '0.375rem',
lg: '0.5rem',
xl: '0.75rem',
'2xl': '1rem',
'3xl': '1.5rem',
full: '9999px'
},
fontWeights: {
body: 400,
heading: 700,
bold: 700,
bold: 700
},
lineHeights: {
one: 1,
body: 1.5,
heading: 0.85,
heading: 0.85
},
letterSpacings: {},
borderWidths: {},
borderStyles: {},
shadows: {},
zIndices: {},
transitions: {},
transitions: {}
},
media: {
sm: "(min-width: 30em)",
md: "(min-width: 48em)",
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)",
sm: '(min-width: 30em)',
md: '(min-width: 48em)',
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)'
},
utils: {
// Abbreviated margin properties
m: (
value: Stitches.ScaleValue<"space"> | Stitches.PropertyValue<"margin">
) => ({
margin: value,
m: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'margin'>) => ({
margin: value
}),
mt: (
value: Stitches.ScaleValue<"space"> | Stitches.PropertyValue<"marginTop">
) => ({
marginTop: value,
mt: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'marginTop'>) => ({
marginTop: value
}),
mr: (
value:
| Stitches.ScaleValue<"space">
| Stitches.PropertyValue<"marginRight">
) => ({
marginRight: value,
mr: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'marginRight'>) => ({
marginRight: value
}),
mb: (
value:
| Stitches.ScaleValue<"space">
| Stitches.PropertyValue<"marginBottom">
) => ({
marginBottom: value,
mb: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'marginBottom'>) => ({
marginBottom: value
}),
ml: (
value: Stitches.ScaleValue<"space"> | Stitches.PropertyValue<"marginLeft">
) => ({
marginLeft: value,
ml: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'marginLeft'>) => ({
marginLeft: value
}),
mx: (
value:
| Stitches.ScaleValue<"space">
| Stitches.PropertyValue<"marginLeft" | "marginRight">
value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'marginLeft' | 'marginRight'>
) => ({
marginLeft: value,
marginRight: value,
marginRight: value
}),
my: (
value:
| Stitches.ScaleValue<"space">
| Stitches.PropertyValue<"marginTop" | "marginBottom">
value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'marginTop' | 'marginBottom'>
) => ({
marginTop: value,
marginBottom: value,
marginBottom: value
}),
// Abbreviated margin properties
p: (
value: Stitches.ScaleValue<"space"> | Stitches.PropertyValue<"padding">
) => ({
padding: value,
p: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'padding'>) => ({
padding: value
}),
pt: (
value: Stitches.ScaleValue<"space"> | Stitches.PropertyValue<"paddingTop">
) => ({
paddingTop: value,
pt: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'paddingTop'>) => ({
paddingTop: value
}),
pr: (
value:
| Stitches.ScaleValue<"space">
| Stitches.PropertyValue<"paddingRight">
) => ({
paddingRight: value,
pr: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'paddingRight'>) => ({
paddingRight: value
}),
pb: (
value:
| Stitches.ScaleValue<"space">
| Stitches.PropertyValue<"paddingBottom">
) => ({
paddingBottom: value,
pb: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'paddingBottom'>) => ({
paddingBottom: value
}),
pl: (
value:
| Stitches.ScaleValue<"space">
| Stitches.PropertyValue<"paddingLeft">
) => ({
paddingLeft: value,
pl: (value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'paddingLeft'>) => ({
paddingLeft: value
}),
px: (
value:
| Stitches.ScaleValue<"space">
| Stitches.PropertyValue<"paddingLeft" | "paddingRight">
value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'paddingLeft' | 'paddingRight'>
) => ({
paddingLeft: value,
paddingRight: value,
paddingRight: value
}),
py: (
value:
| Stitches.ScaleValue<"space">
| Stitches.PropertyValue<"paddingTop" | "paddingBottom">
value: Stitches.ScaleValue<'space'> | Stitches.PropertyValue<'paddingTop' | 'paddingBottom'>
) => ({
paddingTop: value,
paddingBottom: 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,
height: value
}),
// color: (value: Stitches.PropertyValue<'color'> | Stitches.PropertyValue<'width' | 'height'> => ({
// color: value
// }),
// A property to apply linear gradient
linearGradient: (value: Stitches.ScaleValue<"space">) => ({
backgroundImage: `linear-gradient(${value})`,
linearGradient: (value: Stitches.ScaleValue<'space'>) => ({
backgroundImage: `linear-gradient(${value})`
}),
// An abbreviated property for border-radius
br: (value: Stitches.ScaleValue<"space">) => ({
borderRadius: value,
}),
},
});
br: (value: Stitches.ScaleValue<'space'>) => ({
borderRadius: value
})
}
})
export const darkTheme = createTheme("dark", {
export const darkTheme = createTheme('dark', {
colors: {
...grayDark,
...blueDark,
@@ -365,24 +315,24 @@ export const darkTheme = createTheme("dark", {
...purpleDark,
...greenDark,
...redDark,
deep: "rgb(10, 10, 10)",
backgroundOverlay: "$mauve5"
deep: 'rgb(10, 10, 10)',
backgroundOverlay: '$mauve5'
// backgroundA: transparentize(0.1, grayDark.gray1),
},
});
}
})
export const globalStyles = globalCss({
// body: { backgroundColor: '$background', color: '$text', fontFamily: 'Helvetica' },
"html, body": {
backgroundColor: "$mauve2",
color: "$mauve12",
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",
},
});
color: 'inherit',
textDecoration: 'none'
}
})

View File

@@ -1,21 +1,21 @@
import { keyframes } from '../stitches.config';
import { keyframes } from '../stitches.config'
export const slideUpAndFade = keyframes({
"0%": { opacity: 0, transform: "translateY(2px)" },
"100%": { opacity: 1, transform: "translateY(0)" },
});
'0%': { opacity: 0, transform: 'translateY(2px)' },
'100%': { opacity: 1, transform: 'translateY(0)' }
})
export const slideRightAndFade = keyframes({
"0%": { opacity: 0, transform: "translateX(-2px)" },
"100%": { opacity: 1, transform: "translateX(0)" },
});
'0%': { opacity: 0, transform: 'translateX(-2px)' },
'100%': { opacity: 1, transform: 'translateX(0)' }
})
export const slideDownAndFade = keyframes({
"0%": { opacity: 0, transform: "translateY(-2px)" },
"100%": { opacity: 1, transform: "translateY(0)" },
});
'0%': { opacity: 0, transform: 'translateY(-2px)' },
'100%': { opacity: 1, transform: 'translateY(0)' }
})
export const slideLeftAndFade = keyframes({
"0%": { opacity: 0, transform: "translateX(2px)" },
"100%": { opacity: 1, transform: "translateX(0)" },
});
'0%': { opacity: 0, transform: 'translateX(2px)' },
'100%': { opacity: 1, transform: 'translateX(0)' }
})

View File

@@ -1,11 +1,7 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@@ -20,13 +16,6 @@
"incremental": true,
"noUnusedLocals": true
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules",
"*.md"
]
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules", "*.md"]
}

View File

@@ -1,13 +1,13 @@
import NextAuth, { User } from "next-auth"
import NextAuth, { User } from 'next-auth'
declare module "next-auth" {
declare module 'next-auth' {
/**
* Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
*/
interface Session {
user: User & {
username: string;
username: string
}
accessToken?: string;
accessToken?: string
}
}

View File

@@ -1,24 +1,20 @@
import { Spec, parse, Problem } from "comment-parser"
import { Spec, parse, Problem } from 'comment-parser'
export const getTags = (source?: string): Spec[] => {
if (!source) return []
const blocks = parse(source)
const tags = blocks.reduce(
(acc, block) => acc.concat(block.tags),
[] as Spec[]
);
const tags = blocks.reduce((acc, block) => acc.concat(block.tags), [] as Spec[])
return tags
}
export const getErrors = (source?: string): Error | undefined => {
if (!source) return undefined
const blocks = parse(source)
const probs = blocks.reduce(
(acc, block) => acc.concat(block.problems),
[] as Problem[]
);
const probs = blocks.reduce((acc, block) => acc.concat(block.problems), [] as Problem[])
if (!probs.length) return undefined
const errors = probs.map(prob => `[${prob.code}] on line ${prob.line}: ${prob.message}`)
const error = new Error(`The following error(s) occurred while parsing JSDOC: \n${errors.join('\n')}`)
const error = new Error(
`The following error(s) occurred while parsing JSDOC: \n${errors.join('\n')}`
)
return error
}

View File

@@ -1,15 +1,15 @@
import { decodeRestrictedBase64ToBytes } from "./decodeRestrictedBase64ToBytes";
import { isZlibData, decompressZlib } from "./zlib";
import { fromByteArray } from "base64-js";
import { decodeRestrictedBase64ToBytes } from './decodeRestrictedBase64ToBytes'
import { isZlibData, decompressZlib } from './zlib'
import { fromByteArray } from 'base64-js'
export async function decodeBinary(input: string): Promise<ArrayBuffer> {
let data = decodeRestrictedBase64ToBytes(input);
let data = decodeRestrictedBase64ToBytes(input)
if (isZlibData(data)) {
data = await decompressZlib(data);
data = await decompressZlib(data)
}
return data.buffer as ArrayBuffer;
return data.buffer as ArrayBuffer
}
export function encodeBinary(input: ArrayBuffer): string {
return fromByteArray(new Uint8Array(input));
return fromByteArray(new Uint8Array(input))
}

View File

@@ -1,44 +1,119 @@
const base64DecodeMap = [ // starts at 0x2B
62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
0, 0, 0, 0, 0, 0, 0, // 0x3A-0x40
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, // 0x5B-0x0x60
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
44, 45, 46, 47, 48, 49, 50, 51
];
const base64DecodeMap = [
// starts at 0x2B
62,
0,
0,
0,
63,
52,
53,
54,
55,
56,
57,
58,
59,
60,
61,
0,
0,
0,
0,
0,
0,
0, // 0x3A-0x40
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
0,
0,
0,
0,
0,
0, // 0x5B-0x0x60
26,
27,
28,
29,
30,
31,
32,
33,
34,
35,
36,
37,
38,
39,
40,
41,
42,
43,
44,
45,
46,
47,
48,
49,
50,
51
]
const base64DecodeMapOffset = 0x2B;
const base64EOF = 0x3D;
const base64DecodeMapOffset = 0x2b
const base64EOF = 0x3d
export function decodeRestrictedBase64ToBytes(encoded: string) {
let ch: any;
let code: any;
let code2: any;
let ch: any
let code: any
let code2: any
const len = encoded.length;
const padding = encoded.charAt(len - 2) === "=" ? 2 : encoded.charAt(len - 1) === "=" ? 1 : 0;
const decoded = new Uint8Array((encoded.length >> 2) * 3 - padding);
const len = encoded.length
const padding = encoded.charAt(len - 2) === '=' ? 2 : encoded.charAt(len - 1) === '=' ? 1 : 0
const decoded = new Uint8Array((encoded.length >> 2) * 3 - padding)
for (let i = 0, j = 0; i < encoded.length;) {
ch = encoded.charCodeAt(i++);
code = base64DecodeMap[ch - base64DecodeMapOffset];
ch = encoded.charCodeAt(i++);
code2 = base64DecodeMap[ch - base64DecodeMapOffset];
decoded[j++] = (code << 2) | ((code2 & 0x30) >> 4);
for (let i = 0, j = 0; i < encoded.length; ) {
ch = encoded.charCodeAt(i++)
code = base64DecodeMap[ch - base64DecodeMapOffset]
ch = encoded.charCodeAt(i++)
code2 = base64DecodeMap[ch - base64DecodeMapOffset]
decoded[j++] = (code << 2) | ((code2 & 0x30) >> 4)
ch = encoded.charCodeAt(i++);
ch = encoded.charCodeAt(i++)
if (ch === base64EOF) {
return decoded;
return decoded
}
code = base64DecodeMap[ch - base64DecodeMapOffset];
decoded[j++] = ((code2 & 0x0f) << 4) | ((code & 0x3c) >> 2);
code = base64DecodeMap[ch - base64DecodeMapOffset]
decoded[j++] = ((code2 & 0x0f) << 4) | ((code & 0x3c) >> 2)
ch = encoded.charCodeAt(i++);
ch = encoded.charCodeAt(i++)
if (ch === base64EOF) {
return decoded;
return decoded
}
code2 = base64DecodeMap[ch - base64DecodeMapOffset];
decoded[j++] = ((code & 0x03) << 6) | code2;
code2 = base64DecodeMap[ch - base64DecodeMapOffset]
decoded[j++] = ((code & 0x03) << 6) | code2
}
return decoded;
return decoded
}

View File

@@ -1,8 +1,17 @@
import toast from 'react-hot-toast';
import { derive, sign } from "xrpl-accountlib"
import state, { IAccount } from "../state"
import toast from 'react-hot-toast'
import { derive, sign } from 'xrpl-accountlib'
import state, { IAccount } from '../state'
const estimateFee = async (tx: Record<string, unknown>, account: IAccount, opts: { silent?: boolean } = {}): Promise<null | { base_fee: string, median_fee: string; minimum_fee: string; open_ledger_fee: string; }> => {
const estimateFee = async (
tx: Record<string, unknown>,
account: IAccount,
opts: { silent?: boolean } = {}
): Promise<null | {
base_fee: string
median_fee: string
minimum_fee: string
open_ledger_fee: string
}> => {
try {
const copyTx = JSON.parse(JSON.stringify(tx))
delete copyTx['SigningPubKey']
@@ -11,17 +20,17 @@ const estimateFee = async (tx: Record<string, unknown>, account: IAccount, opts:
}
const keypair = derive.familySeed(account.secret)
const { signedTransaction } = sign(copyTx, keypair);
const { signedTransaction } = sign(copyTx, keypair)
const res = await state.client?.send({ command: 'fee', tx_blob: signedTransaction })
if (res && res.drops) {
return res.drops;
return res.drops
}
return null
} catch (err) {
if (!opts.silent) {
console.error(err)
toast.error("Cannot estimate fee.") // ? Some better msg
toast.error('Cannot estimate fee.') // ? Some better msg
}
return null
}

View File

@@ -9,7 +9,7 @@ export const guessZipFileName = (files: File[]) => {
}
export const capitalize = (value?: string) => {
if (!value) return '';
if (!value) return ''
return value[0].toLocaleUpperCase() + value.slice(1);
return value[0].toLocaleUpperCase() + value.slice(1)
}

View File

@@ -24,23 +24,22 @@ export const tts = {
ttNFTOKEN_CREATE_OFFER: 27,
ttNFTOKEN_CANCEL_OFFER: 28,
ttNFTOKEN_ACCEPT_OFFER: 29
};
}
export type TTS = typeof tts;
export type TTS = typeof tts
const calculateHookOn = (arr: (keyof TTS)[]) => {
let start = '0x000000003e3ff5bf';
let start = '0x000000003e3ff5bf'
arr.forEach(n => {
let v = BigInt(start);
v ^= (BigInt(1) << BigInt(tts[n as keyof TTS]));
let s = v.toString(16);
let l = s.length;
if (l < 16)
s = '0'.repeat(16 - l) + s;
s = '0x' + s;
start = s;
let v = BigInt(start)
v ^= BigInt(1) << BigInt(tts[n as keyof TTS])
let s = v.toString(16)
let l = s.length
if (l < 16) s = '0'.repeat(16 - l) + s
s = '0x' + s
start = s
})
return start.substring(2);
return start.substring(2)
}
export default calculateHookOn

View File

@@ -1,31 +1,32 @@
export const extractJSON = (str?: string) => {
if (!str) return
let firstOpen = 0, firstClose = 0, candidate = '';
firstOpen = str.indexOf('{', firstOpen + 1);
let firstOpen = 0,
firstClose = 0,
candidate = ''
firstOpen = str.indexOf('{', firstOpen + 1)
do {
firstClose = str.lastIndexOf('}');
firstClose = str.lastIndexOf('}')
if (firstClose <= firstOpen) {
return;
return
}
do {
candidate = str.substring(firstOpen, firstClose + 1);
candidate = str.substring(firstOpen, firstClose + 1)
try {
let result = JSON.parse(candidate);
let result = JSON.parse(candidate)
return { result, start: firstOpen < 0 ? 0 : firstOpen, end: firstClose }
}
catch (e) { }
firstClose = str.substring(0, firstClose).lastIndexOf('}');
} while (firstClose > firstOpen);
firstOpen = str.indexOf('{', firstOpen + 1);
} while (firstOpen != -1);
} catch (e) {}
firstClose = str.substring(0, firstClose).lastIndexOf('}')
} while (firstClose > firstOpen)
firstOpen = str.indexOf('{', firstOpen + 1)
} while (firstOpen != -1)
}
export const parseJSON = (str?: string | null): any | undefined => {
if (!str) return undefined
try {
const parsed = JSON.parse(str);
return typeof parsed === "object" ? parsed : undefined;
const parsed = JSON.parse(str)
return typeof parsed === 'object' ? parsed : undefined
} catch (error) {
return undefined;
return undefined
}
}

View File

@@ -1,11 +1,16 @@
import { MessageConnection } from "@codingame/monaco-jsonrpc";
import { MonacoLanguageClient, ErrorAction, CloseAction, createConnection } from "@codingame/monaco-languageclient";
import normalizeUrl from "normalize-url";
import ReconnectingWebSocket from "reconnecting-websocket";
import { MessageConnection } from '@codingame/monaco-jsonrpc'
import {
MonacoLanguageClient,
ErrorAction,
CloseAction,
createConnection
} from '@codingame/monaco-languageclient'
import normalizeUrl from 'normalize-url'
import ReconnectingWebSocket from 'reconnecting-websocket'
export function createLanguageClient(connection: MessageConnection): MonacoLanguageClient {
return new MonacoLanguageClient({
name: "Clangd Language Client",
name: 'Clangd Language Client',
clientOptions: {
// use a language id as a document selector
documentSelector: ['c', 'h'],
@@ -15,8 +20,7 @@ export function createLanguageClient(connection: MessageConnection): MonacoLangu
closed: () => {
return CloseAction.DoNotRestart
}
},
}
},
// create a language client connection from the JSON RPC connection on demand
connectionProvider: {
@@ -24,12 +28,12 @@ export function createLanguageClient(connection: MessageConnection): MonacoLangu
return Promise.resolve(createConnection(connection, errorHandler, closeHandler))
}
}
});
})
}
export function createUrl(path: string): string {
const protocol = location.protocol === 'https:' ? 'wss' : 'ws';
return normalizeUrl(`${protocol}://${location.host}${location.pathname}${path}`);
const protocol = location.protocol === 'https:' ? 'wss' : 'ws'
return normalizeUrl(`${protocol}://${location.host}${location.pathname}${path}`)
}
export function createWebSocket(url: string) {
@@ -40,6 +44,6 @@ export function createWebSocket(url: string) {
connectionTimeout: 10000,
maxRetries: Infinity,
debug: false
};
return new ReconnectingWebSocket(url, [], socketOptions);
}
return new ReconnectingWebSocket(url, [], socketOptions)
}

View File

@@ -1,24 +1,21 @@
export const deepEqual = (object1: any, object2: any) => {
if (!isObject(object1) || !isObject(object2)) return object1 === object2
const keys1 = Object.keys(object1);
const keys2 = Object.keys(object2);
const keys1 = Object.keys(object1)
const keys2 = Object.keys(object2)
if (keys1.length !== keys2.length) {
return false;
return false
}
for (const key of keys1) {
const val1 = object1[key];
const val2 = object2[key];
const areObjects = isObject(val1) && isObject(val2);
if (
areObjects && !deepEqual(val1, val2) ||
!areObjects && val1 !== val2
) {
return false;
const val1 = object1[key]
const val2 = object2[key]
const areObjects = isObject(val1) && isObject(val2)
if ((areObjects && !deepEqual(val1, val2)) || (!areObjects && val1 !== val2)) {
return false
}
}
return true;
return true
}
export const isObject = (object: any) => {
return object != null && typeof object === 'object';
return object != null && typeof object === 'object'
}

View File

@@ -1,19 +1,15 @@
export const extractSchemaProps = <O extends object>(obj: O) =>
Object.entries(obj).reduce((prev, [key, val]) => {
const typeOf = <T>(arg: T) =>
arg instanceof Array
? "array"
: arg === null
? "undefined"
: typeof arg;
arg instanceof Array ? 'array' : arg === null ? 'undefined' : typeof arg
const value = (typeOf(val) === "object" && '$type' in val && '$value' in val) ? val?.$value : val;
const type = typeOf(value);
const value = typeOf(val) === 'object' && '$type' in val && '$value' in val ? val?.$value : val
const type = typeOf(value)
let schema: any = {
title: key,
type,
default: value,
default: value
}
if (typeOf(value) === 'array') {
@@ -28,12 +24,11 @@ export const extractSchemaProps = <O extends object>(obj: O) =>
// TODO support primitive-value arrays
}
if (typeOf(value) === "object") {
if (typeOf(value) === 'object') {
schema.properties = extractSchemaProps(value)
}
return {
...prev,
[key]: schema,
};
}, {} as any);
[key]: schema
}
}, {} as any)

View File

@@ -1,56 +1,55 @@
import { getTags } from './comment-parser';
import { tts, TTS } from './hookOnCalculator';
import { getTags } from './comment-parser'
import { tts, TTS } from './hookOnCalculator'
export const transactionOptions = Object.keys(tts).map(key => ({
label: key,
value: key as keyof TTS,
}));
value: key as keyof TTS
}))
export type SetHookData = {
Invoke: {
value: keyof TTS;
label: string;
}[];
Fee: string;
HookNamespace: string;
value: keyof TTS
label: string
}[]
Fee: string
HookNamespace: string
HookParameters: {
HookParameter: {
HookParameterName: string;
HookParameterValue: string;
};
$metaData?: any;
}[];
HookParameterName: string
HookParameterValue: string
}
$metaData?: any
}[]
// HookGrants: {
// HookGrant: {
// Authorize: string;
// HookHash: string;
// };
// }[];
};
}
export const getParameters = (content?: string) => {
const fieldTags = ["field", "param", "arg", "argument"];
const fieldTags = ['field', 'param', 'arg', 'argument']
const tags = getTags(content)
.filter(tag => fieldTags.includes(tag.tag))
.filter(tag => !!tag.name);
.filter(tag => !!tag.name)
const paramters: SetHookData["HookParameters"] = tags.map(tag => ({
const paramters: SetHookData['HookParameters'] = tags.map(tag => ({
HookParameter: {
HookParameterName: tag.name,
HookParameterValue: tag.default || "",
HookParameterValue: tag.default || ''
},
$metaData: {
description: tag.description,
required: !tag.optional
},
}));
}
}))
return paramters;
};
return paramters
}
export const getInvokeOptions = (content?: string) => {
const invokeTags = ["invoke", "invoke-on"];
const invokeTags = ['invoke', 'invoke-on']
const options = getTags(content)
.filter(tag => invokeTags.includes(tag.tag))
@@ -62,7 +61,6 @@ export const getInvokeOptions = (content?: string) => {
}, [] as (keyof TTS)[])
.filter(opt => Object.keys(tts).includes(opt))
const invokeOptions: SetHookData['Invoke'] = options.map(opt => ({
label: opt,
value: opt
@@ -70,9 +68,9 @@ export const getInvokeOptions = (content?: string) => {
// default
if (!invokeOptions.length) {
const payment = transactionOptions.find(tx => tx.value === "ttPAYMENT")
const payment = transactionOptions.find(tx => tx.value === 'ttPAYMENT')
if (payment) return [payment]
}
return invokeOptions;
};
return invokeOptions
}

View File

@@ -1,8 +1,8 @@
const truncate = (str: string, max: number = 8) => {
const array = str.trim().split('');
const ellipsis = array.length > max ? '...' : '';
const array = str.trim().split('')
const ellipsis = array.length > max ? '...' : ''
return array.slice(0, max).join('') + ellipsis;
};
return array.slice(0, max).join('') + ellipsis
}
export default truncate

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More