Compare commits
20 Commits
feat/tab-r
...
fix/tab-sw
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f5a9731bb | ||
|
|
ef4f95ca3e | ||
|
|
fb9814ec76 | ||
|
|
d459b2ee92 | ||
|
|
6ee1a09aaa | ||
|
|
dd2228fb35 | ||
|
|
ca52a5e064 | ||
|
|
df0f8abe62 | ||
|
|
a6c4db1951 | ||
|
|
1c91003164 | ||
|
|
66be0efbbd | ||
|
|
9ab64ec062 | ||
|
|
e77a5e234f | ||
|
|
d2f618512a | ||
|
|
1ee8dcb536 | ||
|
|
b2b7059774 | ||
|
|
41ba096ef9 | ||
|
|
8b72086c04 | ||
|
|
895b34cc68 | ||
|
|
3897f2d823 |
@@ -24,13 +24,19 @@ import { saveAllFiles } from "../state/actions/saveFile";
|
|||||||
import { Tab, Tabs } from "./Tabs";
|
import { Tab, Tabs } from "./Tabs";
|
||||||
import { renameFile } from "../state/actions/createNewFile";
|
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[] } = {};
|
||||||
@@ -138,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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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,23 +129,19 @@ 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) {
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
const res = await estimateFee(tx, account);
|
|
||||||
if (res && res.base_fee) {
|
|
||||||
setValue("Fee", Math.round(Number(res.base_fee || "")).toString());
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
const res = await estimateFee(tx, account);
|
||||||
}, [formInitialized]);
|
if (res && res.base_fee) {
|
||||||
|
setValue("Fee", Math.round(Number(res.base_fee || "")).toString());
|
||||||
|
}
|
||||||
|
}, [account, getValues, setValue]);
|
||||||
|
|
||||||
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}>
|
||||||
<Input
|
<Flex column>
|
||||||
// important to include key with field's id
|
<Flex row>
|
||||||
placeholder="Parameter name"
|
<Input
|
||||||
{...register(
|
// important to include key with field's id
|
||||||
`HookParameters.${index}.HookParameter.HookParameterName`
|
placeholder="Parameter name"
|
||||||
|
readOnly={field.$metaData?.required}
|
||||||
|
{...register(
|
||||||
|
`HookParameters.${index}.HookParameter.HookParameterName`
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
css={{ mx: "$2" }}
|
||||||
|
placeholder="Value (hex-quoted)"
|
||||||
|
{...register(
|
||||||
|
`HookParameters.${index}.HookParameter.HookParameterValue`,
|
||||||
|
{ required: field.$metaData?.required }
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<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" }}>
|
||||||
<Input
|
{capitalize(field.$metaData?.description)}
|
||||||
placeholder="Value (hex-quoted)"
|
</Label>
|
||||||
{...register(
|
</Flex>
|
||||||
`HookParameters.${index}.HookParameter.HookParameterValue`
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Button onClick={() => remove(index)} variant="destroy">
|
|
||||||
<Trash weight="regular" size="16px" />
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
))}
|
))}
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ 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
|
||||||
@@ -98,16 +99,15 @@ export const Tabs = ({
|
|||||||
}, [tabname, setTabnameError]);
|
}, [tabname, setTabnameError]);
|
||||||
|
|
||||||
const validateTabname = useCallback(
|
const validateTabname = useCallback(
|
||||||
(tabname: string): { error?: string, result?: string } => {
|
(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 =
|
let ext = (tabname.includes(".") && tabname.split(".").pop()) || "";
|
||||||
(tabname.includes(".") && tabname.split(".").pop()) || "";
|
|
||||||
|
|
||||||
if (!ext && defaultExtension) {
|
if (!ext && defaultExtension) {
|
||||||
ext = defaultExtension
|
ext = defaultExtension;
|
||||||
tabname = `${tabname}.${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.` };
|
||||||
@@ -153,16 +153,23 @@ export const Tabs = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { result: _tabname = tabname } = res
|
const { result: nwName = tabname } = res;
|
||||||
|
|
||||||
setRenamingTab(null);
|
setRenamingTab(null);
|
||||||
setTabname("");
|
setTabname("");
|
||||||
|
|
||||||
const oldName = tabs[renamingTab]?.header;
|
const oldName = tabs[renamingTab]?.header;
|
||||||
onRenameTab?.(renamingTab, _tabname, oldName);
|
onRenameTab?.(renamingTab, nwName, oldName);
|
||||||
|
|
||||||
handleActiveChange(renamingTab);
|
handleActiveChange(renamingTab, nwName);
|
||||||
}, [handleActiveChange, onRenameTab, renamingTab, tabname, tabs, validateTabname]);
|
}, [
|
||||||
|
handleActiveChange,
|
||||||
|
onRenameTab,
|
||||||
|
renamingTab,
|
||||||
|
tabname,
|
||||||
|
tabs,
|
||||||
|
validateTabname,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleCreateTab = useCallback(() => {
|
const handleCreateTab = useCallback(() => {
|
||||||
const res = validateTabname(tabname);
|
const res = validateTabname(tabname);
|
||||||
@@ -170,7 +177,7 @@ export const Tabs = ({
|
|||||||
setTabnameError(`Error: ${res.error}`);
|
setTabnameError(`Error: ${res.error}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { result: _tabname = tabname } = res
|
const { result: _tabname = tabname } = res;
|
||||||
|
|
||||||
setIsNewtabDialogOpen(false);
|
setIsNewtabDialogOpen(false);
|
||||||
setTabname("");
|
setTabname("");
|
||||||
@@ -178,17 +185,22 @@ export const Tabs = ({
|
|||||||
onCreateNewTab?.(_tabname);
|
onCreateNewTab?.(_tabname);
|
||||||
|
|
||||||
handleActiveChange(tabs.length, _tabname);
|
handleActiveChange(tabs.length, _tabname);
|
||||||
}, [validateTabname, tabname, onCreateNewTab, handleActiveChange, tabs.length]);
|
}, [
|
||||||
|
validateTabname,
|
||||||
|
tabname,
|
||||||
|
onCreateNewTab,
|
||||||
|
handleActiveChange,
|
||||||
|
tabs.length,
|
||||||
|
]);
|
||||||
|
|
||||||
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]
|
||||||
);
|
);
|
||||||
@@ -200,13 +212,16 @@ export const Tabs = ({
|
|||||||
key: "close",
|
key: "close",
|
||||||
onSelect: () => handleCloseTab(idx),
|
onSelect: () => handleCloseTab(idx),
|
||||||
};
|
};
|
||||||
const renameOption = (idx: number): Nullable<ContentMenuOption> =>
|
const renameOption = (idx: number, tab: TabProps): Nullable<ContentMenuOption> => {
|
||||||
onRenameTab && {
|
return (
|
||||||
type: "text",
|
onRenameTab && !tab.renameDisabled && {
|
||||||
label: "Rename",
|
type: "text",
|
||||||
key: "rename",
|
label: "Rename",
|
||||||
onSelect: () => setRenamingTab(idx),
|
key: "rename",
|
||||||
};
|
onSelect: () => setRenamingTab(idx),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -225,7 +240,7 @@ export const Tabs = ({
|
|||||||
<ContextMenu
|
<ContextMenu
|
||||||
key={tab.header}
|
key={tab.header}
|
||||||
options={
|
options={
|
||||||
[closeOption(idx), renameOption(idx)].filter(
|
[closeOption(idx), renameOption(idx, tab)].filter(
|
||||||
Boolean
|
Boolean
|
||||||
) as ContentMenuOption[]
|
) as ContentMenuOption[]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,40 +28,34 @@ 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`, {
|
method: "POST",
|
||||||
method: "POST",
|
});
|
||||||
});
|
const json: FaucetAccountRes | { error: string } = await res.json();
|
||||||
const json: FaucetAccountRes | { error: string } = await res.json();
|
if ("error" in json) {
|
||||||
if ("error" in json) {
|
if (showToast) {
|
||||||
if (showToast) {
|
return toast.error(json.error, { id: toastId });
|
||||||
return toast.error(json.error, { id: toastId });
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (showToast) {
|
return;
|
||||||
toast.success("New account created", { id: toastId });
|
|
||||||
}
|
|
||||||
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(),
|
|
||||||
address: json.address,
|
|
||||||
secret: json.secret,
|
|
||||||
sequence: 1,
|
|
||||||
hooks: [],
|
|
||||||
isLoading: false,
|
|
||||||
version: '2'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (showToast) {
|
||||||
|
toast.success("New account created", { id: toastId });
|
||||||
|
}
|
||||||
|
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(),
|
||||||
|
address: json.address,
|
||||||
|
secret: json.secret,
|
||||||
|
sequence: 1,
|
||||||
|
hooks: [],
|
||||||
|
isLoading: false,
|
||||||
|
version: '2'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -29,25 +29,30 @@ export const compileCode = async (activeId: number) => {
|
|||||||
const file = state.files[activeId]
|
const file = state.files[activeId]
|
||||||
try {
|
try {
|
||||||
file.containsErrors = false
|
file.containsErrors = false
|
||||||
const res = await fetch(process.env.NEXT_PUBLIC_COMPILE_API_ENDPOINT, {
|
let res: Response
|
||||||
method: "POST",
|
try {
|
||||||
headers: {
|
res = await fetch(process.env.NEXT_PUBLIC_COMPILE_API_ENDPOINT, {
|
||||||
"Content-Type": "application/json",
|
method: "POST",
|
||||||
},
|
headers: {
|
||||||
body: JSON.stringify({
|
"Content-Type": "application/json",
|
||||||
output: "wasm",
|
},
|
||||||
compress: true,
|
body: JSON.stringify({
|
||||||
strip: state.compileOptions.strip,
|
output: "wasm",
|
||||||
files: [
|
compress: true,
|
||||||
{
|
strip: state.compileOptions.strip,
|
||||||
type: "c",
|
files: [
|
||||||
options: state.compileOptions.optimizationLevel || '-O2',
|
{
|
||||||
name: file.name,
|
type: "c",
|
||||||
src: file.content,
|
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!")
|
||||||
|
}
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
state.compiling = false;
|
state.compiling = false;
|
||||||
if (!json.success) {
|
if (!json.success) {
|
||||||
@@ -61,29 +66,34 @@ export const compileCode = async (activeId: number) => {
|
|||||||
}
|
}
|
||||||
throw errors
|
throw errors
|
||||||
}
|
}
|
||||||
state.logs.push({
|
try {
|
||||||
type: "success",
|
// Decode base64 encoded wasm that is coming back from the endpoint
|
||||||
message: `File ${state.files?.[activeId]?.name} compiled successfully. Ready to deploy.`,
|
const bufferData = await decodeBinary(json.output);
|
||||||
link: Router.asPath.replace("develop", "deploy"),
|
|
||||||
linkText: "Go to deploy",
|
// Import wabt from and create human readable version of wasm file and
|
||||||
});
|
// put it into state
|
||||||
// Decode base64 encoded wasm that is coming back from the endpoint
|
const ww = (await import('wabt')).default()
|
||||||
const bufferData = await decodeBinary(json.output);
|
|
||||||
file.compiledContent = ref(bufferData);
|
|
||||||
file.lastCompiled = new Date();
|
|
||||||
file.compiledValueSnapshot = file.content
|
|
||||||
// Import wabt from and create human readable version of wasm file and
|
|
||||||
// put it into state
|
|
||||||
import("wabt").then((wabt) => {
|
|
||||||
const ww = wabt.default();
|
|
||||||
const myModule = ww.readWasm(new Uint8Array(bufferData), {
|
const myModule = ww.readWasm(new Uint8Array(bufferData), {
|
||||||
readDebugNames: true,
|
readDebugNames: true,
|
||||||
});
|
});
|
||||||
myModule.applyNames();
|
myModule.applyNames();
|
||||||
|
|
||||||
const wast = myModule.toText({ foldExprs: false, inlineExport: false });
|
const wast = myModule.toText({ foldExprs: false, inlineExport: false });
|
||||||
state.files[state.active].compiledWatContent = wast;
|
|
||||||
toast.success("Compiled successfully!", { position: "bottom-center" });
|
file.compiledContent = ref(bufferData);
|
||||||
|
file.lastCompiled = new Date();
|
||||||
|
file.compiledValueSnapshot = file.content
|
||||||
|
file.compiledWatContent = wast;
|
||||||
|
} catch (error) {
|
||||||
|
throw Error("Invalid compilation result produced, check your code for errors and try again!")
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success("Compiled successfully!", { position: "bottom-center" });
|
||||||
|
state.logs.push({
|
||||||
|
type: "success",
|
||||||
|
message: `File ${state.files?.[activeId]?.name} compiled successfully. Ready to deploy.`,
|
||||||
|
link: Router.asPath.replace("develop", "deploy"),
|
||||||
|
linkText: "Go to deploy",
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
@@ -96,12 +106,19 @@ export const compileCode = async (activeId: number) => {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
else if (err instanceof Error) {
|
||||||
|
state.logs.push({
|
||||||
|
type: "error",
|
||||||
|
message: err.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
state.logs.push({
|
state.logs.push({
|
||||||
type: "error",
|
type: "error",
|
||||||
message: "Something went wrong, check your connection try again later!",
|
message: "Something went wrong, come back later!",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
state.compiling = false;
|
state.compiling = false;
|
||||||
toast.error(`Error occurred while compiling!`, { position: "bottom-center" });
|
toast.error(`Error occurred while compiling!`, { position: "bottom-center" });
|
||||||
file.containsErrors = true
|
file.containsErrors = true
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
78
utils/setHook.ts
Normal file
78
utils/setHook.ts
Normal 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;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user