Compare commits

...

23 Commits

Author SHA1 Message Date
Valtteri Karesto
58cde29fff Update next-auth to fix dependabot alert 2022-08-08 09:07:45 +03:00
muzamil
a06fb06610 Merge pull request #264 from XRPLF/fix/tab-switching
Switch to correct tab after renaming/closing.
2022-08-05 16:23:38 +05:30
muzam1l
1f5a9731bb Readonly files are not renamable now! 2022-08-04 18:54:20 +05:30
muzam1l
ef4f95ca3e Switch to correct tab after renaming/closing. 2022-08-03 16:02:17 +05:30
muzamil
fb9814ec76 Merge pull request #262 from XRPLF/fix/script-log-appending
Clear script log on running new script.
2022-08-03 14:33:59 +05:30
muzam1l
d459b2ee92 Clear script log on running new script. 2022-08-01 17:04:26 +05:30
muzamil
6ee1a09aaa Merge pull request #259 from XRPLF/feat/deploy-default-fields
Deploy config default fields from source files.
2022-07-29 15:59:01 +05:30
muzam1l
dd2228fb35 Reset deploy form fields when file changes. 2022-07-29 14:33:28 +05:30
muzam1l
ca52a5e064 Enforce required prop in default tags. 2022-07-28 18:12:58 +05:30
muzam1l
df0f8abe62 Add required margin to param field. 2022-07-27 17:31:43 +05:30
muzam1l
a6c4db1951 Invoke options defaults. 2022-07-26 17:05:33 +05:30
muzam1l
1c91003164 Deploy config default fields from source files. 2022-07-25 20:22:58 +05:30
muzamil
66be0efbbd Merge pull request #255 from XRPLF/feat/tab-renames
Implement file renaming.
2022-07-22 16:35:47 +05:30
muzamil
9ab64ec062 Merge pull request #256 from XRPLF/fix/compile-result
Fix incorrect compilation result.
2022-07-22 14:43:55 +05:30
muzamil
e77a5e234f Merge pull request #257 from XRPLF/fix/account-select
Fixes #240.
2022-07-22 14:43:32 +05:30
muzamil
d2f618512a Merge pull request #258 from XRPLF/fix/acc-import-limit
Remove account import limit.
2022-07-22 14:43:01 +05:30
muzam1l
f5063de2c9 Fix matching file error algo for rename/newfile. 2022-07-22 14:40:40 +05:30
muzam1l
1ee8dcb536 Select comp: remove hightlight from selected option 2022-07-21 16:58:07 +05:30
muzam1l
7f6f9c11db Fix dialog z-index for safari. 2022-07-21 16:44:33 +05:30
muzam1l
b2b7059774 Remove account import limit. 2022-07-21 15:31:06 +05:30
muzam1l
41ba096ef9 Account selector change. 2022-07-21 15:26:37 +05:30
muzam1l
b9da659f83 Fix that weird anonymous zero in the ui. 2022-07-21 14:14:40 +05:30
muzam1l
6a3ff3e1d7 Implement tab renaming. 2022-07-20 16:56:55 +05:30
16 changed files with 388 additions and 196 deletions

View File

@@ -15,37 +15,11 @@ import {
ContextMenuTriggerItem, ContextMenuTriggerItem,
} from "./primitive"; } from "./primitive";
[
{
label: "Show bookmarks",
type: "checkbox",
checked: true,
indicator: "*",
onCheckedChange: () => {},
},
{
type: "radio",
label: "People",
value: "pedro",
onValueChange: () => {},
options: [
{
value: "pedro",
label: "Pedro Duarte",
},
{
value: "colm",
label: "Colm Tuite",
},
],
},
];
export type TextOption = { export type TextOption = {
type: "text"; type: "text";
label: ReactNode; label: ReactNode;
onClick?: () => any; onSelect?: () => any;
children?: Option[]; children?: ContentMenuOption[];
}; };
export type SeparatorOption = { type: "separator" }; export type SeparatorOption = { type: "separator" };
export type CheckboxOption = { export type CheckboxOption = {
@@ -64,11 +38,16 @@ export type RadioOption<T extends string = string> = {
type WithCommons = { key: string; disabled?: boolean }; type WithCommons = { key: string; disabled?: boolean };
type Option = (TextOption | SeparatorOption | CheckboxOption | RadioOption) & export type ContentMenuOption = (
| TextOption
| SeparatorOption
| CheckboxOption
| RadioOption
) &
WithCommons; WithCommons;
interface IContextMenu { export interface IContextMenu {
options?: Option[]; options?: ContentMenuOption[];
isNested?: boolean; isNested?: boolean;
} }
export const ContextMenu: FC<IContextMenu> = ({ export const ContextMenu: FC<IContextMenu> = ({
@@ -83,11 +62,11 @@ export const ContextMenu: FC<IContextMenu> = ({
) : ( ) : (
<ContextMenuTrigger>{children}</ContextMenuTrigger> <ContextMenuTrigger>{children}</ContextMenuTrigger>
)} )}
{options && ( {options && !!options.length && (
<ContextMenuContent sideOffset={isNested ? 2 : 5}> <ContextMenuContent sideOffset={isNested ? 2 : 5}>
{options.map(({ key, ...option }) => { {options.map(({ key, ...option }) => {
if (option.type === "text") { if (option.type === "text") {
const { children, label } = option; const { children, label, onSelect } = option;
if (children) if (children)
return ( return (
<ContextMenu isNested key={key} options={children}> <ContextMenu isNested key={key} options={children}>
@@ -97,7 +76,11 @@ export const ContextMenu: FC<IContextMenu> = ({
</Flex> </Flex>
</ContextMenu> </ContextMenu>
); );
return <ContextMenuItem key={key}>{label}</ContextMenuItem>; return (
<ContextMenuItem key={key} onSelect={onSelect}>
{label}
</ContextMenuItem>
);
} }
if (option.type === "checkbox") { if (option.type === "checkbox") {
const { label, checked, onCheckedChange } = option; const { label, checked, onCheckedChange } = option;

View File

@@ -15,7 +15,7 @@ const contentShow = keyframes({
"100%": { opacity: 1 }, "100%": { opacity: 1 },
}); });
const StyledOverlay = styled(DialogPrimitive.Overlay, { const StyledOverlay = styled(DialogPrimitive.Overlay, {
zIndex: 9999, zIndex: 10000,
backgroundColor: blackA.blackA9, backgroundColor: blackA.blackA9,
position: "fixed", position: "fixed",
inset: 0, inset: 0,

View File

@@ -22,14 +22,21 @@ import docs from "../xrpl-hooks-docs/docs";
import Monaco from "./Monaco"; import Monaco from "./Monaco";
import { saveAllFiles } from "../state/actions/saveFile"; import { saveAllFiles } from "../state/actions/saveFile";
import { Tab, Tabs } from "./Tabs"; import { Tab, Tabs } from "./Tabs";
import { renameFile } from "../state/actions/createNewFile";
const validateWritability = (editor: monaco.editor.IStandaloneCodeEditor) => { const checkWritable = (filename?: string): boolean => {
const currPath = editor.getModel()?.uri.path; if (apiHeaderFiles.find(file => file === filename)) {
if (apiHeaderFiles.find(h => currPath?.endsWith(h))) { return false;
editor.updateOptions({ readOnly: true });
} else {
editor.updateOptions({ readOnly: false });
} }
return true;
};
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[] } = {};
@@ -129,6 +136,7 @@ const HooksEditor = () => {
extensionRequired extensionRequired
onCreateNewTab={createNewFile} onCreateNewTab={createNewFile}
onCloseTab={idx => state.files.splice(idx, 1)} onCloseTab={idx => state.files.splice(idx, 1)}
onRenameTab={(idx, nwName, oldName = "") => renameFile(oldName, nwName)}
headerExtraValidation={{ headerExtraValidation={{
regex: /^[A-Za-z0-9_-]+[.][A-Za-z0-9]{1,4}$/g, regex: /^[A-Za-z0-9_-]+[.][A-Za-z0-9]{1,4}$/g,
error: error:
@@ -136,7 +144,7 @@ const HooksEditor = () => {
}} }}
> >
{snap.files.map((file, index) => { {snap.files.map((file, index) => {
return <Tab key={file.name} header={file.name} />; return <Tab key={file.name} header={file.name} renameDisabled={!checkWritable(file.name)} />;
})} })}
</Tabs> </Tabs>
); );

View File

@@ -143,7 +143,6 @@ const RunScript: React.FC<{ file: IFile }> = ({ file: { content, name } }) => {
setIframeCode(template); setIframeCode(template);
state.scriptLogs = [ state.scriptLogs = [
...snap.scriptLogs,
{ type: "success", message: "Started running..." }, { type: "success", message: "Started running..." },
]; ];
} catch (err) { } catch (err) {

View File

@@ -91,7 +91,7 @@ const Select = forwardRef<any, Props>((props, ref) => {
...provided, ...provided,
color: colors.searchText, color: colors.searchText,
backgroundColor: backgroundColor:
state.isSelected || state.isFocused state.isFocused
? colors.activeLight ? colors.activeLight
: colors.dropDownBg, : colors.dropDownBg,
":hover": { ":hover": {

View File

@@ -1,7 +1,6 @@
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import { Plus, Trash, X } from "phosphor-react"; import { Plus, Trash, X } from "phosphor-react";
import Button from "./Button"; import { Button, Box, Text } from ".";
import Box from "./Box";
import { Stack, Flex, Select } from "."; import { Stack, Flex, Select } from ".";
import { import {
Dialog, Dialog,
@@ -19,48 +18,30 @@ import {
useForm, useForm,
} from "react-hook-form"; } from "react-hook-form";
import { TTS, tts } from "../utils/hookOnCalculator";
import { deployHook } from "../state/actions"; import { deployHook } from "../state/actions";
import { useSnapshot } from "valtio"; import { useSnapshot } from "valtio";
import state, { IFile, SelectOption } from "../state"; import state, { IFile, SelectOption } from "../state";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { prepareDeployHookTx, sha256 } from "../state/actions/deployHook"; import { prepareDeployHookTx, sha256 } from "../state/actions/deployHook";
import estimateFee from "../utils/estimateFee"; import estimateFee from "../utils/estimateFee";
import {
const transactionOptions = Object.keys(tts).map(key => ({ getParameters,
label: key, getInvokeOptions,
value: key as keyof TTS, transactionOptions,
})); SetHookData,
} from "../utils/setHook";
export type SetHookData = { import { capitalize } from "../utils/helpers";
Invoke: {
value: keyof TTS;
label: string;
}[];
Fee: string;
HookNamespace: string;
HookParameters: {
HookParameter: {
HookParameterName: string;
HookParameterValue: string;
};
}[];
// HookGrants: {
// HookGrant: {
// Authorize: string;
// HookHash: string;
// };
// }[];
};
export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo( export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
({ accountAddress }) => { ({ accountAddress }) => {
const snap = useSnapshot(state); const snap = useSnapshot(state);
const [estimateLoading, setEstimateLoading] = useState(false);
const [isSetHookDialogOpen, setIsSetHookDialogOpen] = useState(false);
const compiledFiles = snap.files.filter(file => file.compiledContent); const compiledFiles = snap.files.filter(file => file.compiledContent);
const activeFile = compiledFiles[snap.activeWat] as IFile | undefined; const activeFile = compiledFiles[snap.activeWat] as IFile | undefined;
const [isSetHookDialogOpen, setIsSetHookDialogOpen] = useState(false);
const accountOptions: SelectOption[] = snap.accounts.map(acc => ({ const accountOptions: SelectOption[] = snap.accounts.map(acc => ({
label: acc.name, label: acc.name,
value: acc.address, value: acc.address,
@@ -75,12 +56,23 @@ export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
const getHookNamespace = useCallback( const getHookNamespace = useCallback(
() => () =>
activeFile && snap.deployValues[activeFile.name] (activeFile && snap.deployValues[activeFile.name]?.HookNamespace) ||
? snap.deployValues[activeFile.name].HookNamespace activeFile?.name.split(".")[0] ||
: activeFile?.name.split(".")[0] || "", "",
[activeFile, snap.deployValues] [activeFile, snap.deployValues]
); );
const getDefaultValues = useCallback((): Partial<SetHookData> => {
const content = activeFile?.compiledValueSnapshot;
return (
(activeFile && snap.deployValues[activeFile.name]) || {
HookNamespace: getHookNamespace(),
Invoke: getInvokeOptions(content),
HookParameters: getParameters(content),
}
);
}, [activeFile, getHookNamespace, snap.deployValues]);
const { const {
register, register,
handleSubmit, handleSubmit,
@@ -88,29 +80,25 @@ export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
watch, watch,
setValue, setValue,
getValues, getValues,
reset,
formState: { errors }, formState: { errors },
} = useForm<SetHookData>({ } = useForm<SetHookData>({
defaultValues: (activeFile && snap.deployValues[activeFile.name]) || { defaultValues: getDefaultValues(),
HookNamespace: activeFile?.name.split(".")[0] || "",
Invoke: transactionOptions.filter(to => to.label === "ttPAYMENT"),
},
}); });
const { fields, append, remove } = useFieldArray({ const { fields, append, remove } = useFieldArray({
control, control,
name: "HookParameters", // unique name for your Field Array name: "HookParameters", // unique name for your Field Array
}); });
const [formInitialized, setFormInitialized] = useState(false);
const [estimateLoading, setEstimateLoading] = useState(false);
const watchedFee = watch("Fee"); const watchedFee = watch("Fee");
// Update value if activeFile changes // Reset form if activeFile changes
useEffect(() => { useEffect(() => {
if (!activeFile) return; if (!activeFile) return;
const defaultValue = getHookNamespace(); const defaultValues = getDefaultValues();
setValue("HookNamespace", defaultValue); reset(defaultValues);
setFormInitialized(true); }, [activeFile, getDefaultValues, reset]);
}, [setValue, activeFile, snap.deployValues, getHookNamespace]);
useEffect(() => { useEffect(() => {
if ( if (
@@ -141,10 +129,9 @@ export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
calculateHashedValue(); calculateHashedValue();
}, [namespace, calculateHashedValue]); }, [namespace, calculateHashedValue]);
// Calculate initial fee estimate when modal opens const calculateFee = useCallback(async () => {
useEffect(() => { if (!account) return;
if (formInitialized && account) {
(async () => {
const formValues = getValues(); const formValues = getValues();
const tx = await prepareDeployHookTx(account, formValues); const tx = await prepareDeployHookTx(account, formValues);
if (!tx) { if (!tx) {
@@ -154,10 +141,7 @@ export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
if (res && res.base_fee) { 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]);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [formInitialized]);
const tooLargeFile = () => { const tooLargeFile = () => {
return Boolean( return Boolean(
@@ -172,6 +156,12 @@ export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
); );
if (!account) return; if (!account) return;
if (currAccount) currAccount.isLoading = true; if (currAccount) currAccount.isLoading = true;
data.HookParameters.forEach(param => {
delete param.$metaData;
return param;
});
const res = await deployHook(account, data); const res = await deployHook(account, data);
if (currAccount) currAccount.isLoading = false; if (currAccount) currAccount.isLoading = false;
@@ -181,8 +171,14 @@ export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
} }
toast.error(`Transaction failed! (${res?.engine_result_message})`); toast.error(`Transaction failed! (${res?.engine_result_message})`);
}; };
const onOpenChange = useCallback((open: boolean) => {
setIsSetHookDialogOpen(open);
if (open) calculateFee();
}, [calculateFee]);
return ( return (
<Dialog open={isSetHookDialogOpen} onOpenChange={setIsSetHookDialogOpen}> <Dialog open={isSetHookDialogOpen} onOpenChange={onOpenChange}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button <Button
ghost ghost
@@ -206,7 +202,6 @@ export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
<Select <Select
instanceId="deploy-account" instanceId="deploy-account"
placeholder="Select account" placeholder="Select account"
hideSelectedOptions
options={accountOptions} options={accountOptions}
value={selectedAccount} value={selectedAccount}
onChange={(acc: any) => setSelectedAccount(acc)} onChange={(acc: any) => setSelectedAccount(acc)}
@@ -252,22 +247,39 @@ export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
<Stack> <Stack>
{fields.map((field, index) => ( {fields.map((field, index) => (
<Stack key={field.id}> <Stack key={field.id}>
<Flex column>
<Flex row>
<Input <Input
// important to include key with field's id // important to include key with field's id
placeholder="Parameter name" placeholder="Parameter name"
readOnly={field.$metaData?.required}
{...register( {...register(
`HookParameters.${index}.HookParameter.HookParameterName` `HookParameters.${index}.HookParameter.HookParameterName`
)} )}
/> />
<Input <Input
css={{ mx: "$2" }}
placeholder="Value (hex-quoted)" placeholder="Value (hex-quoted)"
{...register( {...register(
`HookParameters.${index}.HookParameter.HookParameterValue` `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" /> <Trash weight="regular" size="16px" />
</Button> </Button>
</Flex>
{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>
</Stack> </Stack>
))} ))}
<Button <Button

View File

@@ -6,7 +6,7 @@ import React, {
useCallback, useCallback,
} from "react"; } from "react";
import type { ReactNode, ReactElement } from "react"; import type { ReactNode, ReactElement } from "react";
import { Box, Button, Flex, Input, Label, Stack, Text } from "."; import { Box, Button, Flex, Input, Label, Pre, Stack, Text } from ".";
import { import {
Dialog, Dialog,
DialogTrigger, DialogTrigger,
@@ -18,7 +18,7 @@ import {
import { Plus, X } from "phosphor-react"; import { Plus, X } from "phosphor-react";
import { styled } from "../stitches.config"; import { styled } from "../stitches.config";
import { capitalize } from "../utils/helpers"; import { capitalize } from "../utils/helpers";
import ContextMenu from "./ContextMenu"; import ContextMenu, { ContentMenuOption } from "./ContextMenu";
const ErrorText = styled(Text, { const ErrorText = styled(Text, {
color: "$error", color: "$error",
@@ -26,9 +26,12 @@ const ErrorText = styled(Text, {
display: "block", display: "block",
}); });
type Nullable<T> = T | null | undefined | false;
interface TabProps { interface TabProps {
header: string; header: string;
children?: ReactNode; children?: ReactNode;
renameDisabled?: boolean
} }
// TODO customize messages shown // TODO customize messages shown
@@ -47,6 +50,7 @@ interface Props {
error: string; error: string;
}; };
onCreateNewTab?: (name: string) => any; onCreateNewTab?: (name: string) => any;
onRenameTab?: (index: number, nwName: string, oldName?: string) => any;
onCloseTab?: (index: number, header?: string) => any; onCloseTab?: (index: number, header?: string) => any;
onChangeActive?: (index: number, header?: string) => any; onChangeActive?: (index: number, header?: string) => any;
} }
@@ -63,6 +67,7 @@ export const Tabs = ({
onCreateNewTab, onCreateNewTab,
onCloseTab, onCloseTab,
onChangeActive, onChangeActive,
onRenameTab,
headerExtraValidation, headerExtraValidation,
extensionRequired, extensionRequired,
defaultExtension = "", defaultExtension = "",
@@ -72,8 +77,9 @@ export const Tabs = ({
const tabs: TabProps[] = children.map(elem => elem.props); const tabs: TabProps[] = children.map(elem => elem.props);
const [isNewtabDialogOpen, setIsNewtabDialogOpen] = useState(false); const [isNewtabDialogOpen, setIsNewtabDialogOpen] = useState(false);
const [renamingTab, setRenamingTab] = useState<number | null>(null);
const [tabname, setTabname] = useState(""); const [tabname, setTabname] = useState("");
const [newtabError, setNewtabError] = useState<string | null>(null); const [tabnameError, setTabnameError] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
if (activeIndex) setActive(activeIndex); if (activeIndex) setActive(activeIndex);
@@ -89,21 +95,23 @@ export const Tabs = ({
// when filename changes, reset error // when filename changes, reset error
useEffect(() => { useEffect(() => {
setNewtabError(null); setTabnameError(null);
}, [tabname, setNewtabError]); }, [tabname, setTabnameError]);
const validateTabname = useCallback( const validateTabname = useCallback(
(tabname: string): { error: string | null } => { (tabname: string): { error?: string; result?: string } => {
if (!tabname) { if (!tabname) {
return { error: `Please enter ${label.toLocaleLowerCase()} name.` }; return { error: `Please enter ${label.toLocaleLowerCase()} name.` };
} }
let ext = (tabname.includes(".") && tabname.split(".").pop()) || "";
if (!ext && defaultExtension) {
ext = defaultExtension;
tabname = `${tabname}.${defaultExtension}`;
}
if (tabs.find(tab => tab.header === tabname)) { if (tabs.find(tab => tab.header === tabname)) {
return { error: `${capitalize(label)} name already exists.` }; return { error: `${capitalize(label)} name already exists.` };
} }
const ext =
(tabname.includes(".") && tabname.split(".").pop()) ||
defaultExtension ||
"";
if (extensionRequired && !ext) { if (extensionRequired && !ext) {
return { error: "File extension is required!" }; return { error: "File extension is required!" };
} }
@@ -116,7 +124,7 @@ export const Tabs = ({
) { ) {
return { error: headerExtraValidation.error }; return { error: headerExtraValidation.error };
} }
return { error: null }; return { result: tabname };
}, },
[ [
allowedExtensions, allowedExtensions,
@@ -136,16 +144,40 @@ export const Tabs = ({
[onChangeActive] [onChangeActive]
); );
const handleCreateTab = useCallback(() => { const handleRenameTab = useCallback(() => {
const chk = validateTabname(tabname); if (renamingTab === null) return;
if (chk.error) {
setNewtabError(`Error: ${chk.error}`); const res = validateTabname(tabname);
if (res.error) {
setTabnameError(`Error: ${res.error}`);
return; return;
} }
let _tabname = tabname;
if (defaultExtension && !_tabname.endsWith(defaultExtension)) { const { result: nwName = tabname } = res;
_tabname = `${_tabname}.${defaultExtension}`;
setRenamingTab(null);
setTabname("");
const oldName = tabs[renamingTab]?.header;
onRenameTab?.(renamingTab, nwName, oldName);
handleActiveChange(renamingTab, nwName);
}, [
handleActiveChange,
onRenameTab,
renamingTab,
tabname,
tabs,
validateTabname,
]);
const handleCreateTab = useCallback(() => {
const res = validateTabname(tabname);
if (res.error) {
setTabnameError(`Error: ${res.error}`);
return;
} }
const { result: _tabname = tabname } = res;
setIsNewtabDialogOpen(false); setIsNewtabDialogOpen(false);
setTabname(""); setTabname("");
@@ -154,9 +186,8 @@ export const Tabs = ({
handleActiveChange(tabs.length, _tabname); handleActiveChange(tabs.length, _tabname);
}, [ }, [
tabname,
defaultExtension,
validateTabname, validateTabname,
tabname,
onCreateNewTab, onCreateNewTab,
handleActiveChange, handleActiveChange,
tabs.length, tabs.length,
@@ -164,17 +195,34 @@ export const Tabs = ({
const handleCloseTab = useCallback( const handleCloseTab = useCallback(
(idx: number) => { (idx: number) => {
if (idx <= active && active !== 0) {
setActive(active - 1);
}
onCloseTab?.(idx, tabs[idx].header); onCloseTab?.(idx, tabs[idx].header);
handleActiveChange(idx, tabs[idx].header); if (idx <= active && active !== 0) {
const nwActive = active - 1
handleActiveChange(nwActive, tabs[nwActive].header);
}
}, },
[active, handleActiveChange, onCloseTab, tabs] [active, handleActiveChange, onCloseTab, tabs]
); );
const closeOption = (idx: number): Nullable<ContentMenuOption> =>
onCloseTab && {
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),
}
);
}
return ( return (
<> <>
{!headless && ( {!headless && (
@@ -189,7 +237,14 @@ export const Tabs = ({
}} }}
> >
{tabs.map((tab, idx) => ( {tabs.map((tab, idx) => (
<ContextMenu key={tab.header}> <ContextMenu
key={tab.header}
options={
[closeOption(idx), renameOption(idx, tab)].filter(
Boolean
) as ContentMenuOption[]
}
>
<Button <Button
role="tab" role="tab"
tabIndex={idx} tabIndex={idx}
@@ -261,7 +316,7 @@ export const Tabs = ({
} }
}} }}
/> />
<ErrorText>{newtabError}</ErrorText> <ErrorText>{tabnameError}</ErrorText>
</DialogDescription> </DialogDescription>
<Flex <Flex
@@ -286,6 +341,51 @@ export const Tabs = ({
</DialogContent> </DialogContent>
</Dialog> </Dialog>
)} )}
{onRenameTab && (
<Dialog
open={renamingTab !== null}
onOpenChange={() => setRenamingTab(null)}
>
<DialogContent>
<DialogTitle>
Rename <Pre>{tabs[renamingTab || 0]?.header}</Pre>
</DialogTitle>
<DialogDescription>
<Label>Enter new name</Label>
<Input
value={tabname}
onChange={e => setTabname(e.target.value)}
onKeyPress={e => {
if (e.key === "Enter") {
handleRenameTab();
}
}}
/>
<ErrorText>{tabnameError}</ErrorText>
</DialogDescription>
<Flex
css={{
marginTop: 25,
justifyContent: "flex-end",
gap: "$3",
}}
>
<DialogClose asChild>
<Button outline>Cancel</Button>
</DialogClose>
<Button variant="primary" onClick={handleRenameTab}>
Confirm
</Button>
</Flex>
<DialogClose asChild>
<Box css={{ position: "absolute", top: "$3", right: "$3" }}>
<X size="20px" />
</Box>
</DialogClose>
</DialogContent>
</Dialog>
)}
</Stack> </Stack>
)} )}
{keepAllAlive {keepAllAlive

View File

@@ -5,7 +5,7 @@ import state from "../../state";
import { import {
defaultTransactionType, defaultTransactionType,
getTxFields, getTxFields,
modifyTransaction, modifyTxState,
prepareState, prepareState,
prepareTransaction, prepareTransaction,
SelectOption, SelectOption,
@@ -42,7 +42,7 @@ const Transaction: FC<TransactionProps> = ({
const setState = useCallback( const setState = useCallback(
(pTx?: Partial<TransactionState>) => { (pTx?: Partial<TransactionState>) => {
return modifyTransaction(header, pTx); return modifyTxState(header, pTx);
}, },
[header] [header]
); );
@@ -164,7 +164,7 @@ const Transaction: FC<TransactionProps> = ({
} }
nwState.txFields = fields; nwState.txFields = fields;
const state = modifyTransaction(header, nwState, { replaceState: true }); const state = modifyTxState(header, nwState, { replaceState: true });
const editorValue = getJsonString(state); const editorValue = getJsonString(state);
return setState({ editorValue }); return setState({ editorValue });
}, },

View File

@@ -36,7 +36,7 @@
"lodash.xor": "^4.5.0", "lodash.xor": "^4.5.0",
"monaco-editor": "^0.33.0", "monaco-editor": "^0.33.0",
"next": "^12.0.4", "next": "^12.0.4",
"next-auth": "^4.10.1", "next-auth": "^4.10.3",
"next-plausible": "^3.2.0", "next-plausible": "^3.2.0",
"next-themes": "^0.1.1", "next-themes": "^0.1.1",
"normalize-url": "^7.0.2", "normalize-url": "^7.0.2",

View File

@@ -3,12 +3,12 @@ import Split from "react-split";
import { useSnapshot } from "valtio"; import { useSnapshot } from "valtio";
import { Box, Container, Flex, Tab, Tabs } from "../../components"; import { Box, Container, Flex, Tab, Tabs } from "../../components";
import Transaction from "../../components/Transaction"; import Transaction from "../../components/Transaction";
import state from "../../state"; import state, { renameTxState } from "../../state";
import { getSplit, saveSplit } from "../../state/actions/persistSplits"; import { getSplit, saveSplit } from "../../state/actions/persistSplits";
import { transactionsState, modifyTransaction } from "../../state"; import { transactionsState, modifyTxState } from "../../state";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { FileJs } from "phosphor-react"; import { FileJs } from "phosphor-react";
import RunScript from '../../components/RunScript'; import RunScript from "../../components/RunScript";
const DebugStream = dynamic(() => import("../../components/DebugStream"), { const DebugStream = dynamic(() => import("../../components/DebugStream"), {
ssr: false, ssr: false,
@@ -96,9 +96,12 @@ const Test = () => {
keepAllAlive keepAllAlive
defaultExtension="json" defaultExtension="json"
allowedExtensions={["json"]} allowedExtensions={["json"]}
onCreateNewTab={header => modifyTransaction(header, {})} onCreateNewTab={header => modifyTxState(header, {})}
onRenameTab={(idx, nwName, oldName = "") =>
renameTxState(oldName, nwName)
}
onCloseTab={(idx, header) => onCloseTab={(idx, header) =>
header && modifyTransaction(header, undefined) header && modifyTxState(header, undefined)
} }
> >
{transactions.map(({ header, state }) => ( {transactions.map(({ header, state }) => (

View File

@@ -28,12 +28,7 @@ export const names = [
* is protected with CORS so that's why we did our own endpoint * is protected with CORS so that's why we did our own endpoint
*/ */
export const addFaucetAccount = async (name?: string, showToast: boolean = false) => { export const addFaucetAccount = async (name?: string, showToast: boolean = false) => {
// Lets limit the number of faucet accounts to 5 for now if (typeof window === undefined) return
if (state.accounts.length > 5) {
return toast.error("You can only have maximum 6 accounts");
}
if (typeof window !== 'undefined') {
const toastId = showToast ? toast.loading("Creating account") : ""; const toastId = showToast ? toast.loading("Creating account") : "";
const res = await fetch(`${window.location.origin}/api/faucet`, { const res = await fetch(`${window.location.origin}/api/faucet`, {
@@ -62,7 +57,6 @@ export const addFaucetAccount = async (name?: string, showToast: boolean = false
version: '2' version: '2'
}); });
} }
}
}; };
// fetch initial faucets // fetch initial faucets

View File

@@ -15,3 +15,10 @@ export const createNewFile = (name: string) => {
state.files.push(emptyFile); state.files.push(emptyFile);
state.active = state.files.length - 1; 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

@@ -3,10 +3,10 @@ import toast from "react-hot-toast";
import state, { IAccount } from "../index"; import state, { IAccount } from "../index";
import calculateHookOn, { TTS } from "../../utils/hookOnCalculator"; import calculateHookOn, { TTS } from "../../utils/hookOnCalculator";
import { SetHookData } from "../../components/SetHookDialog";
import { Link } from "../../components"; import { Link } from "../../components";
import { ref } from "valtio"; import { ref } from "valtio";
import estimateFee from "../../utils/estimateFee"; import estimateFee from "../../utils/estimateFee";
import { SetHookData } from '../../utils/setHook';
export const sha256 = async (string: string) => { export const sha256 = async (string: string) => {
const utf8 = new TextEncoder().encode(string); const utf8 = new TextEncoder().encode(string);

View File

@@ -48,13 +48,21 @@ export const transactionsState = proxy({
activeHeader: "test1.json" activeHeader: "test1.json"
}); });
export const renameTxState = (oldName: string, nwName: string) => {
const tx = transactionsState.transactions.find(tx => tx.header === oldName);
if (!tx) throw Error(`No transaction state exists with given header name ${oldName}`);
tx.header = nwName
}
/** /**
* Simple transaction state changer * Simple transaction state changer
* @param header Unique key and tab name for the transaction tab * @param header Unique key and tab name for the transaction tab
* @param partialTx partial transaction state, `undefined` deletes the transaction * @param partialTx partial transaction state, `undefined` deletes the transaction
* *
*/ */
export const modifyTransaction = ( export const modifyTxState = (
header: string, header: string,
partialTx?: Partial<TransactionState>, partialTx?: Partial<TransactionState>,
opts: { replaceState?: boolean } = {} opts: { replaceState?: boolean } = {}

78
utils/setHook.ts Normal file
View File

@@ -0,0 +1,78 @@
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,
}));
export type SetHookData = {
Invoke: {
value: keyof TTS;
label: string;
}[];
Fee: string;
HookNamespace: string;
HookParameters: {
HookParameter: {
HookParameterName: string;
HookParameterValue: string;
};
$metaData?: any;
}[];
// HookGrants: {
// HookGrant: {
// Authorize: string;
// HookHash: string;
// };
// }[];
};
export const getParameters = (content?: string) => {
const fieldTags = ["field", "param", "arg", "argument"];
const tags = getTags(content)
.filter(tag => fieldTags.includes(tag.tag))
.filter(tag => !!tag.name);
const paramters: SetHookData["HookParameters"] = tags.map(tag => ({
HookParameter: {
HookParameterName: tag.name,
HookParameterValue: tag.default || "",
},
$metaData: {
description: tag.description,
required: !tag.optional
},
}));
return paramters;
};
export const getInvokeOptions = (content?: string) => {
const invokeTags = ["invoke", "invoke-on"];
const options = getTags(content)
.filter(tag => invokeTags.includes(tag.tag))
.reduce((cumm, curr) => {
const combined = curr.type || `${curr.name} ${curr.description}`
const opts = combined.split(' ')
return cumm.concat(opts as any)
}, [] as (keyof TTS)[])
.filter(opt => Object.keys(tts).includes(opt))
const invokeOptions: SetHookData['Invoke'] = options.map(opt => ({
label: opt,
value: opt
}))
// default
if (!invokeOptions.length) {
const payment = transactionOptions.find(tx => tx.value === "ttPAYMENT")
if (payment) return [payment]
}
return invokeOptions;
};

View File

@@ -2953,10 +2953,10 @@ natural-compare@^1.4.0:
resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
next-auth@^4.10.1: next-auth@^4.10.3:
version "4.10.1" version "4.10.3"
resolved "https://registry.yarnpkg.com/next-auth/-/next-auth-4.10.1.tgz#33b29265d12287bb2f6d267c8d415a407c27f0e9" resolved "https://registry.yarnpkg.com/next-auth/-/next-auth-4.10.3.tgz#0a952dd5004fd2ac2ba414c990922cf9b33951a3"
integrity sha512-F00vtwBdyMIIJ8IORHOAOHjVGTDEhhm9+HpB2BQ8r40WtGxqToWWLN7Z+2ZW/z2RFlo3zhcuAtUCPUzVJxtZwQ== integrity sha512-7zc4aXYc/EEln7Pkcsn21V1IevaTZsMLJwapfbnKA4+JY0+jFzWbt5p/ljugesGIrN4VOZhpZIw50EaFZyghJQ==
dependencies: dependencies:
"@babel/runtime" "^7.16.3" "@babel/runtime" "^7.16.3"
"@panva/hkdf" "^1.0.1" "@panva/hkdf" "^1.0.1"