Compare commits

..

11 Commits

Author SHA1 Message Date
JaniAnttonen
8430c9b553 Use explicit types 2022-04-05 15:04:24 +03:00
JaniAnttonen
1c66c9e572 Let logs through even when there are no accounts 2022-04-05 12:45:20 +03:00
JaniAnttonen
2b518f80e5 Listen to connection disposals 2022-04-05 12:44:14 +03:00
Vaclav Barta
234832138f fixes #155 2022-04-01 08:42:10 +02:00
Valtteri Karesto
28d94a1475 Merge pull request #152 from eqlabs/fix/cloud-upload-button
Fix/cloud upload button
2022-03-31 10:15:31 +03:00
Valtteri Karesto
594aee6cd2 Merge pull request #154 from eqlabs/feat/add-header-templates
Feat/add header templates
2022-03-30 12:31:08 +03:00
Valtteri Karesto
d75910972f Change order 2022-03-30 12:09:16 +03:00
Valtteri Karesto
589c604a12 Add header files as hard coded 2022-03-30 12:00:27 +03:00
Valtteri Karesto
8394a11705 Merge pull request #151 from eqlabs/fix/disable-delete-hook
Fixes issue #148
2022-03-29 23:34:12 +03:00
Valtteri Karesto
5aeed7c246 Few more changes to deleteHook function 2022-03-29 15:23:30 +03:00
Valtteri Karesto
8d03edc299 Fixes issue #148 2022-03-29 14:03:06 +03:00
11 changed files with 1496 additions and 74 deletions

View File

@@ -249,6 +249,7 @@ export const AccountDialog = ({
<Button <Button
size="xs" size="xs"
outline outline
disabled={activeAccount.isLoading}
css={{ mt: "$3", mr: "$1", ml: "auto" }} css={{ mt: "$3", mr: "$1", ml: "auto" }}
onClick={() => { onClick={() => {
deleteHook(activeAccount); deleteHook(activeAccount);

View File

@@ -1,28 +1,29 @@
import React, { useEffect, useRef } from "react"; import { listen } from "@codingame/monaco-jsonrpc";
import { useSnapshot, ref } from "valtio"; import { MonacoServices } from "@codingame/monaco-languageclient";
import Editor, { loader } from "@monaco-editor/react"; import Editor, { loader } from "@monaco-editor/react";
import uniqBy from "lodash.uniqby";
import type monaco from "monaco-editor"; import type monaco from "monaco-editor";
import { ArrowBendLeftUp } from "phosphor-react";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import uniqBy from "lodash.uniqby"; import { ArrowBendLeftUp } from "phosphor-react";
import React, { useEffect, useRef } from "react";
import Box from "./Box"; import toast from "react-hot-toast";
import Container from "./Container"; import ReconnectingWebSocket from "reconnecting-websocket";
import dark from "../theme/editor/amy.json"; import { ref, useSnapshot } from "valtio";
import light from "../theme/editor/xcode_default.json"; import state from "../state";
import { saveFile } from "../state/actions"; import { saveFile } from "../state/actions";
import { apiHeaderFiles } from "../state/constants"; import { apiHeaderFiles } from "../state/constants";
import state from "../state"; import dark from "../theme/editor/amy.json";
import light from "../theme/editor/xcode_default.json";
import { createLanguageClient, createWebSocket } from "../utils/languageClient";
import docs from "../xrpl-hooks-docs/docs";
import Box from "./Box";
import Container from "./Container";
import EditorNavigation from "./EditorNavigation"; import EditorNavigation from "./EditorNavigation";
import Text from "./Text"; 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";
loader.config({ loader.config({
paths: { paths: {
@@ -123,6 +124,7 @@ const HooksEditor = () => {
setMarkers(monacoRef.current); setMarkers(monacoRef.current);
} }
}, [snap.active]); }, [snap.active]);
return ( return (
<Box <Box
css={{ css={{
@@ -155,7 +157,7 @@ const HooksEditor = () => {
); );
} }
// create the web socket // create the websocket
if (!subscriptionRef.current) { if (!subscriptionRef.current) {
monaco.languages.register({ monaco.languages.register({
id: "c", id: "c",
@@ -164,11 +166,12 @@ const HooksEditor = () => {
mimetypes: ["text/plain"], mimetypes: ["text/plain"],
}); });
MonacoServices.install(monaco); MonacoServices.install(monaco);
const webSocket = createWebSocket(
const webSocket: ReconnectingWebSocket = createWebSocket(
process.env.NEXT_PUBLIC_LANGUAGE_SERVER_API_ENDPOINT || "" process.env.NEXT_PUBLIC_LANGUAGE_SERVER_API_ENDPOINT || ""
); );
subscriptionRef.current = webSocket; subscriptionRef.current = webSocket;
// listen when the web socket is opened // listen when the websocket is opened
listen({ listen({
webSocket: webSocket as WebSocket, webSocket: webSocket as WebSocket,
onConnection: (connection) => { onConnection: (connection) => {
@@ -180,9 +183,14 @@ const HooksEditor = () => {
// disposable.stop(); // disposable.stop();
disposable.dispose(); disposable.dispose();
} catch (err) { } catch (err) {
console.log("err", err); toast.error('Connection to language server lost!')
console.error("Couldn't dispose the language server connection! ", err);
} }
}); });
connection.onDispose(() => {
toast.error('Connection to language server lost!')
})
// TODO: Check if we need to listen to more connection events
}, },
}); });
} }

View File

@@ -1,22 +1,17 @@
import {
useRef,
useLayoutEffect,
ReactNode,
FC,
useState,
useCallback,
} from "react";
import { Notepad, Prohibit } from "phosphor-react";
import useStayScrolled from "react-stay-scrolled";
import NextLink from "next/link"; import NextLink from "next/link";
import { Notepad, Prohibit } from "phosphor-react";
import Container from "./Container"; import {
import LogText from "./LogText"; FC, ReactNode, useCallback, useLayoutEffect, useRef, useState
import state, { ILog } from "../state"; } from "react";
import { Pre, Link, Heading, Button, Text, Flex, Box } from "."; import useStayScrolled from "react-stay-scrolled";
import regexifyString from "regexify-string"; import regexifyString from "regexify-string";
import { useSnapshot } from "valtio"; import { useSnapshot } from "valtio";
import { Box, Button, Flex, Heading, Link, Pre, Text } from ".";
import state, { ILog } from "../state";
import { AccountDialog } from "./Accounts"; import { AccountDialog } from "./Accounts";
import Container from "./Container";
import LogText from "./LogText";
interface ILogBox { interface ILogBox {
title: string; title: string;
@@ -160,7 +155,7 @@ export const Log: FC<ILog> = ({
const enrichAccounts = useCallback( const enrichAccounts = useCallback(
(str?: string): ReactNode => { (str?: string): ReactNode => {
if (!str || !accounts.length) return null; if (!str) return null;
const pattern = `(${accounts.map((acc) => acc.address).join("|")})`; const pattern = `(${accounts.map((acc) => acc.address).join("|")})`;
const res = regexifyString({ const res = regexifyString({

View File

@@ -228,7 +228,7 @@ const Navigation = () => {
as="a" as="a"
rel="noreferrer noopener" rel="noreferrer noopener"
target="_blank" target="_blank"
href="https://xrpl-hooks.readme.io/docs" href="https://xrpl-hooks.readme.io/v2.0/docs"
> >
<ArrowUpRight size="15px" /> Hooks documentation <ArrowUpRight size="15px" /> Hooks documentation
</Text> </Text>
@@ -400,7 +400,7 @@ const Navigation = () => {
</Button> </Button>
</Link> </Link>
</ButtonGroup> </ButtonGroup>
<Link href="https://xrpl-hooks.readme.io/" passHref> <Link href="https://xrpl-hooks.readme.io/v2.0" passHref>
<a target="_blank" rel="noreferrer noopener"> <a target="_blank" rel="noreferrer noopener">
<Button outline> <Button outline>
<BookOpen size="15px" /> <BookOpen size="15px" />

View File

@@ -33,8 +33,6 @@ export const addFaucetAccount = async (showToast: boolean = false) => {
return toast.error("You can only have maximum 6 accounts"); return toast.error("You can only have maximum 6 accounts");
} }
if (typeof window !== 'undefined') { 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",
@@ -92,5 +90,4 @@ export const addFunds = async (address: string) => {
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 Router from 'next/router';
import toast from "react-hot-toast";
import { ref } from "valtio";
import { decodeBinary } from "../../utils/decodeBinary";
import state from "../index"; import state from "../index";
import { saveFile } from "./saveFile"; import { saveFile } from "./saveFile";
import { decodeBinary } from "../../utils/decodeBinary";
import { ref } from "valtio";
/* compileCode sends the code of the active file to compile endpoint /* compileCode sends the code of the active file to compile endpoint
* If all goes well you will get base64 encoded wasm file back with * If all goes well you will get base64 encoded wasm file back with
@@ -15,17 +15,22 @@ import { ref } from "valtio";
export const compileCode = async (activeId: number) => { export const compileCode = async (activeId: number) => {
// Save the file to global state // Save the file to global state
saveFile(false); saveFile(false);
if (!process.env.NEXT_PUBLIC_COMPILE_API_ENDPOINT) { if (!process.env.NEXT_PUBLIC_COMPILE_API_ENDPOINT) {
throw Error("Missing env!"); throw Error("Missing env!");
} }
// Bail out if we're already compiling // Bail out if we're already compiling
if (state.compiling) { if (state.compiling) {
// if compiling is ongoing return // if compiling is ongoing return
return; return;
} }
// Set loading state to true // Set loading state to true
state.compiling = true; state.compiling = true;
// Reset development log
state.logs = [] state.logs = []
try { try {
const res = await fetch(process.env.NEXT_PUBLIC_COMPILE_API_ENDPOINT, { const res = await fetch(process.env.NEXT_PUBLIC_COMPILE_API_ENDPOINT, {
method: "POST", method: "POST",
@@ -46,6 +51,7 @@ export const compileCode = async (activeId: number) => {
}); });
const json = await res.json(); const json = await res.json();
state.compiling = false; state.compiling = false;
if (!json.success) { if (!json.success) {
state.logs.push({ type: "error", message: json.message }); state.logs.push({ type: "error", message: json.message });
if (json.tasks && json.tasks.length > 0) { if (json.tasks && json.tasks.length > 0) {
@@ -57,16 +63,19 @@ export const compileCode = async (activeId: number) => {
} }
return toast.error(`Couldn't compile!`, { position: "bottom-center" }); return toast.error(`Couldn't compile!`, { position: "bottom-center" });
} }
state.logs.push({ state.logs.push({
type: "success", type: "success",
message: `File ${state.files?.[activeId]?.name} compiled successfully. Ready to deploy.`, message: `File ${state.files?.[activeId]?.name} compiled successfully. Ready to deploy.`,
link: Router.asPath.replace("develop", "deploy"), link: Router.asPath.replace("develop", "deploy"),
linkText: "Go to deploy", linkText: "Go to deploy",
}); });
// Decode base64 encoded wasm that is coming back from the endpoint // Decode base64 encoded wasm that is coming back from the endpoint
const bufferData = await decodeBinary(json.output); const bufferData = await decodeBinary(json.output);
state.files[state.active].compiledContent = ref(bufferData); state.files[state.active].compiledContent = ref(bufferData);
state.files[state.active].lastCompiled = new Date(); state.files[state.active].lastCompiled = new Date();
// Import wabt from and create human readable version of wasm file and // Import wabt from and create human readable version of wasm file and
// put it into state // put it into state
import("wabt").then((wabt) => { import("wabt").then((wabt) => {
@@ -80,12 +89,22 @@ export const compileCode = async (activeId: number) => {
state.files[state.active].compiledWatContent = wast; state.files[state.active].compiledWatContent = wast;
toast.success("Compiled successfully!", { position: "bottom-center" }); toast.success("Compiled successfully!", { position: "bottom-center" });
}); });
} catch (err) { } catch (err: any) {
console.log(err); const error = err as Error
state.logs.push({
type: "error", // TODO: Centralized error handling? Just a thought
message: "Error occured while compiling!", if(error.message.includes("Failed to fetch")) {
}); state.logs.push({
type: "error",
message: "No connection to the compiler server!",
});
} else {
state.logs.push({
type: "error",
message: "Error occured while compiling!",
});
}
state.compiling = false; state.compiling = false;
} }
}; };

View File

@@ -1,9 +1,10 @@
import { derive, sign } from "xrpl-accountlib";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { derive, sign } from "xrpl-accountlib";
import state, { IAccount } from "../index"; import { AnyJson } from "xrpl-client";
import calculateHookOn, { TTS } from "../../utils/hookOnCalculator";
import { SetHookData } from "../../components/SetHookDialog"; import { SetHookData } from "../../components/SetHookDialog";
import calculateHookOn, { TTS } from "../../utils/hookOnCalculator";
import state, { IAccount } from "../index";
const hash = async (string: string) => { const hash = async (string: string) => {
const utf8 = new TextEncoder().encode(string); const utf8 = new TextEncoder().encode(string);
@@ -51,19 +52,22 @@ function arrayBufferToHex(arrayBuffer?: ArrayBuffer | null) {
* hex string, signs the transaction and deploys it to * hex string, signs the transaction and deploys it to
* Hooks testnet. * Hooks testnet.
*/ */
export const deployHook = async (account: IAccount & { name?: string }, data: SetHookData) => { export const deployHook = async (account: IAccount & { name?: string }, data: SetHookData): Promise<AnyJson | undefined> => {
if ( if (
!state.files || !state.files ||
state.files.length === 0 || state.files.length === 0 ||
!state.files?.[state.active]?.compiledContent !state.files?.[state.active]?.compiledContent
) { ) {
console.log("ebin1")
return; return;
} }
if (!state.files?.[state.active]?.compiledContent) { if (!state.files?.[state.active]?.compiledContent) {
console.log("ebin2")
return; return;
} }
if (!state.client) { if (!state.client) {
console.log("ebin3")
return; return;
} }
const HookNamespace = await hash(arrayBufferToHex( const HookNamespace = await hash(arrayBufferToHex(
@@ -136,6 +140,7 @@ export const deployHook = async (account: IAccount & { name?: string }, data: Se
}); });
} }
} catch (err) { } catch (err) {
console.log("Ebin")
console.log(err); console.log(err);
state.deployLogs.push({ state.deployLogs.push({
type: "error", type: "error",
@@ -153,13 +158,18 @@ export const deleteHook = async (account: IAccount & { name?: string }) => {
if (!state.client) { if (!state.client) {
return; return;
} }
const currentAccount = state.accounts.find(
(acc) => acc.address === account.address
);
if (currentAccount?.isLoading || !currentAccount?.hooks.length) {
return
}
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
const tx = { const tx = {
Account: account.address, Account: account.address,
TransactionType: "SetHook", TransactionType: "SetHook",
Sequence: account.sequence, Sequence: account.sequence,
Fee: "1000000", Fee: "100000",
Hooks: [ Hooks: [
{ {
Hook: { Hook: {
@@ -172,9 +182,7 @@ export const deleteHook = async (account: IAccount & { name?: string }) => {
const keypair = derive.familySeed(account.secret); const keypair = derive.familySeed(account.secret);
const { signedTransaction } = sign(tx, keypair); const { signedTransaction } = sign(tx, keypair);
const currentAccount = state.accounts.find(
(acc) => acc.address === account.address
);
if (currentAccount) { if (currentAccount) {
currentAccount.isLoading = true; currentAccount.isLoading = true;
} }
@@ -196,6 +204,7 @@ export const deleteHook = async (account: IAccount & { name?: string }) => {
type: "success", type: "success",
message: `[${submitRes.engine_result}] ${submitRes.engine_result_message} Validated ledger index: ${submitRes.validated_ledger_index}`, message: `[${submitRes.engine_result}] ${submitRes.engine_result_message} Validated ledger index: ${submitRes.validated_ledger_index}`,
}); });
currentAccount.hooks = [];
} else { } 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({ state.deployLogs.push({
@@ -216,4 +225,4 @@ export const deleteHook = async (account: IAccount & { name?: string }) => {
} }
return submitRes; return submitRes;
} }
}; };

View File

@@ -2,6 +2,7 @@ import { Octokit } from "@octokit/core";
import Router from "next/router"; import Router from "next/router";
import state from '../index'; import state from '../index';
import { templateFileIds } from '../constants'; import { templateFileIds } from '../constants';
import { hookapiH, hookmacroH, sfcodesH } from '../constants/headerTemplates';
const octokit = new Octokit(); const octokit = new Octokit();
@@ -24,16 +25,26 @@ export const fetchFiles = (gistId: string) => {
return res return res
} }
// in case of templates, fetch header file(s) and append to res // in case of templates, fetch header file(s) and append to res
return octokit.request("GET /gists/{gist_id}", { gist_id: templateFileIds.headers }).then(({ data: { files: headerFiles } }) => { const files = {
const files = { ...res.data.files, ...headerFiles } ...res.data.files,
res.data.files = files 'hookapi.h': res.data.files?.['hookapi.h'] || { filename: 'hookapi.h', content: hookapiH, language: 'C' },
return res 'hookmacro.h': res.data.files?.['hookmacro.h'] || { filename: 'hookmacro.h', content: hookmacroH, language: 'C' },
}) 'sfcodes.h': res.data.files?.['sfcodes.h'] || { filename: 'sfcodes.h', content: sfcodesH, language: 'C' },
};
res.data.files = files;
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 }
// console.log(headerFiles)
// res.data.files = files
// return res
// })
}) })
.then((res) => { .then((res) => {
if (res.data.files && Object.keys(res.data.files).length > 0) { if (res.data.files && Object.keys(res.data.files).length > 0) {
const files = Object.keys(res.data.files).map((filename) => ({ const files = Object.keys(res.data.files).map((filename) => ({
name: res.data.files?.[filename]?.filename || "noname.c", name: res.data.files?.[filename]?.filename || "untitled.c",
language: res.data.files?.[filename]?.language?.toLowerCase() || "", language: res.data.files?.[filename]?.language?.toLowerCase() || "",
content: res.data.files?.[filename]?.content || "", content: res.data.files?.[filename]?.content || "",
})); }));

File diff suppressed because it is too large Load Diff

View File

@@ -14,8 +14,7 @@ export const templateFileIds = {
'carbon': 'a9fbcaf1b816b198c7fc0f62962bebf2', 'carbon': 'a9fbcaf1b816b198c7fc0f62962bebf2',
'doubler': '56b86174aeb70b2b48eee962bad3e355', 'doubler': '56b86174aeb70b2b48eee962bad3e355',
'peggy': 'd21298a37e1550b781682014762a567b', 'peggy': 'd21298a37e1550b781682014762a567b',
'headers': '9b448e8a55fab11ef5d1274cb59f9cf3' 'headers': '55f639bce59a49c58c45e663776b5138'
} }
export const apiHeaderFiles = ['hookapi.h', 'sfcodes.h', 'hookmacro.h'] export const apiHeaderFiles = ['hookapi.h', 'sfcodes.h', 'hookmacro.h']

View File

@@ -1,5 +1,5 @@
import { MessageConnection } from "@codingame/monaco-jsonrpc"; import { MessageConnection } from "@codingame/monaco-jsonrpc";
import { MonacoLanguageClient, ErrorAction, CloseAction, createConnection } from "@codingame/monaco-languageclient"; import { CloseAction, createConnection, ErrorAction, MonacoLanguageClient } from "@codingame/monaco-languageclient";
import Router from "next/router"; import Router from "next/router";
import normalizeUrl from "normalize-url"; import normalizeUrl from "normalize-url";
import ReconnectingWebSocket from "reconnecting-websocket"; import ReconnectingWebSocket from "reconnecting-websocket";
@@ -37,7 +37,7 @@ export function createUrl(path: string): string {
return normalizeUrl(`${protocol}://${location.host}${location.pathname}${path}`); return normalizeUrl(`${protocol}://${location.host}${location.pathname}${path}`);
} }
export function createWebSocket(url: string) { export function createWebSocket(url: string): ReconnectingWebSocket {
const socketOptions = { const socketOptions = {
maxReconnectionDelay: 10000, maxReconnectionDelay: 10000,
minReconnectionDelay: 1000, minReconnectionDelay: 1000,
@@ -47,4 +47,4 @@ export function createWebSocket(url: string) {
debug: false debug: false
}; };
return new ReconnectingWebSocket(url, [], socketOptions); return new ReconnectingWebSocket(url, [], socketOptions);
} }