Skip to content

Commit

Permalink
feat: add tabs and openrpdocument autocomplete
Browse files Browse the repository at this point in the history
  • Loading branch information
shanejonas committed Mar 5, 2020
1 parent 933e143 commit 1f4aae4
Show file tree
Hide file tree
Showing 8 changed files with 408 additions and 57 deletions.
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"react-json-view": "^1.19.1",
"react-split-pane": "^0.1.87",
"semantic-release": "^15.13.21",
"use-dark-mode": "^2.3.1"
"use-dark-mode": "^2.3.1",
"use-debounce": "^3.3.0"
}
}
16 changes: 3 additions & 13 deletions src/containers/App.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,15 @@
import React, { useEffect } from "react";
import { CssBaseline } from "@material-ui/core";
import { MuiThemeProvider } from "@material-ui/core";

import { lightTheme, darkTheme } from "../themes/openrpcTheme";
import useDarkMode from "use-dark-mode";
import Inspector from "./Inspector";
import useQueryParams from "../hooks/useQueryParams";
import * as monaco from "monaco-editor";
import localStorageMock from "../helpers/localStorageMock";

let localStorageEnabled = true;
try {
window.localStorage.setItem("xyz-test", "true");
} catch (e) {
localStorageEnabled = false;
console.error(e);
}

// mock storageProvider for when localStorage is not available via chrome/brave settings
const darkModeOptions = localStorageEnabled ? undefined : localStorageMock;

const App: React.FC = () => {
const darkMode = useDarkMode(undefined, darkModeOptions);
const darkMode = useDarkMode();
const [query] = useQueryParams();
const theme = darkMode.value ? darkTheme : lightTheme;
useEffect(() => {
Expand All @@ -31,6 +20,7 @@ const App: React.FC = () => {
return (
<MuiThemeProvider theme={theme}>
<CssBaseline />

<Inspector
onToggleDarkMode={darkMode.toggle}
darkMode={darkMode.value}
Expand Down
144 changes: 134 additions & 10 deletions src/containers/Inspector.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import React, { useState, useEffect, ChangeEvent, Dispatch, useRef } from "react";
import SplitPane from "react-split-pane";
import _ from "lodash";
import JSONRPCRequestEditor from "./JSONRPCRequestEditor";
import PlayCircle from "@material-ui/icons/PlayCircleFilled";
import { IconButton, AppBar, Toolbar, Typography, Button, InputBase } from "@material-ui/core";
import CloseIcon from "@material-ui/icons/Close";
import PlusIcon from "@material-ui/icons/Add";
import { IconButton, AppBar, Toolbar, Typography, Button, InputBase, Tab, Tabs } from "@material-ui/core";
import { Client, RequestManager, HTTPTransport, WebSocketTransport } from "@open-rpc/client-js";
import Brightness3Icon from "@material-ui/icons/Brightness3";
import WbSunnyIcon from "@material-ui/icons/WbSunny";
import { JSONRPCError } from "@open-rpc/client-js/build/Error";
import { MethodObject } from "@open-rpc/meta-schema";
import MonacoEditor from "@etclabscore/react-monaco-editor";
import useTabs from "../hooks/useTabs";
import { useDebounce } from "use-debounce";

const errorToJSON = (error: JSONRPCError | undefined): any => {
if (!error) {
Expand Down Expand Up @@ -48,7 +53,6 @@ const useClient = (url: string): [Client, JSONRPCError | undefined, Dispatch<JSO
setClient(c);
c.onError((e) => {
console.log("onError", e); //tslint:disable-line
setError(e);
});
} catch (e) {
setError(e);
Expand All @@ -67,8 +71,29 @@ function useCounter(defaultValue: number): [number, () => void] {
return [counter, incrementCounter];
}

const emptyJSONRPC = {
jsonrpc: "2.0",
method: "",
params: [],
id: "0",
};

const Inspector: React.FC<IProps> = (props) => {
const {
setTabContent,
setTabEditing,
setTabIndex,
tabs,
setTabs,
handleClose,
tabIndex,
setTabOpenRPCDocument,
setTabUrl,
handleLabelChange,
setTabResults,
} = useTabs();
const [id, incrementId] = useCounter(0);
const [openrpcDocument, setOpenRpcDocument] = useState();
const [json, setJson] = useState(props.request || {
jsonrpc: "2.0",
method: "",
Expand All @@ -78,6 +103,7 @@ const Inspector: React.FC<IProps> = (props) => {
const editorRef = useRef();
const [results, setResults] = useState();
const [url, setUrl] = useState(props.url || "");
const [debouncedUrl] = useDebounce(url, 1000);
const [client, error, setError] = useClient(url);
useEffect(() => {
if (props.openrpcMethodObject) {
Expand All @@ -92,18 +118,26 @@ const Inspector: React.FC<IProps> = (props) => {
}, []);
useEffect(() => {
if (json) {
setJson({
const jsonResult = {
...json,
jsonrpc: "2.0",
id: id.toString(),
});
};
setJson(jsonResult);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id]);

useEffect(() => {
if (json) {
setTabContent(tabIndex, json);
}
}, [json]);

useEffect(() => {
if (props.url) {
setUrl(props.url);
setTabUrl(tabIndex, props.url);
}
}, [props.url]);

Expand All @@ -113,19 +147,22 @@ const Inspector: React.FC<IProps> = (props) => {
incrementId();
try {
const result = await client.request(json.method, json.params);
setResults({ jsonrpc: "2.0", result, id });
const r = { jsonrpc: "2.0", result, id };
setResults(r);
setTabResults(tabIndex, r);
} catch (e) {
setError(e);
setResults(e);
setTabResults(tabIndex, e);
}
}
};
function handleResponseEditorDidMount(_: any, editor: any) {
function handleResponseEditorDidMount(__: any, editor: any) {
editorRef.current = editor;
}

const clear = () => {
setResults(undefined);
setError(undefined);
setTabResults(tabIndex, undefined);
};

const handleClearButton = () => {
Expand All @@ -137,9 +174,83 @@ const Inspector: React.FC<IProps> = (props) => {
props.onToggleDarkMode();
}
};
const refreshOpenRpcDocument = async () => {
if (url) {
try {
const d = await client.request("rpc.discover", []);
setOpenRpcDocument(d);
setTabOpenRPCDocument(tabIndex, d);
} catch (e) {
setOpenRpcDocument(undefined);
setTabOpenRPCDocument(tabIndex, undefined);
}
}
};

useEffect(() => {
refreshOpenRpcDocument();
}, [debouncedUrl]);

useEffect(() => {
if (tabs[tabIndex]) {
setJson(tabs[tabIndex].content);
setUrl(tabs[tabIndex].url || "");
setOpenRpcDocument(tabs[tabIndex].openrpcDocument);
setResults(tabs[tabIndex].results);
}
}, [tabIndex]);

const handleTabIndexChange = (event: React.ChangeEvent<{}>, newValue: number) => {
setTabIndex(newValue);
};

return (
<>
<div style={{ position: "relative" }}>
<Tabs
style={{ background: "transparent" }}
value={tabIndex}
variant="scrollable"
indicatorColor="primary"
onChange={handleTabIndexChange}
>
{tabs.map((tab, index) => (
<Tab disableRipple style={{
border: "none",
outline: "none",
userSelect: "none",
}} onDoubleClick={() => setTabEditing(tab, true)} label={
<div style={{ userSelect: "none" }}>
{tab.editing
? <InputBase
value={tab.name}
onChange={(ev) => handleLabelChange(ev, tab)}
onBlur={() => setTabEditing(tab, false)}
autoFocus
style={{ maxWidth: "80px", marginRight: "25px" }}
/>
: <Typography style={{ display: "inline", textTransform: "none", marginRight: "25px" }} variant="body1" >{tab.name}</Typography>
}
{tabIndex === index
?
<IconButton onClick={
(ev) => handleClose(ev, index)
} style={{ position: "absolute", right: "10px", top: "25%" }} size="small">
<CloseIcon />
</IconButton>
: null
}
</div>
}></Tab>
))}
<Tab disableRipple style={{ minWidth: "50px" }} label={
<IconButton onClick={() => setTabs([...tabs, { name: "New Tab", content: { ...emptyJSONRPC }, url: "" }])}>
<PlusIcon scale={0.5} />
</IconButton>
}>
</Tab>
</Tabs>
</div>
<AppBar elevation={0} position="static">
<Toolbar>
<img
Expand All @@ -156,7 +267,10 @@ const Inspector: React.FC<IProps> = (props) => {
value={url}
placeholder="Enter a JSON-RPC server URL"
onChange={
(event: ChangeEvent<HTMLInputElement>) => setUrl(event.target.value)
(event: ChangeEvent<HTMLInputElement>) => {
setUrl(event.target.value);
setTabUrl(tabIndex, event.target.value);
}
}
fullWidth
style={{ background: "rgba(0,0,0,0.1)", borderRadius: "4px", padding: "0px 10px", marginRight: "5px" }}
Expand All @@ -183,8 +297,18 @@ const Inspector: React.FC<IProps> = (props) => {
}}>
<JSONRPCRequestEditor
onChange={(val) => {
setJson(JSON.parse(val));
let jsonResult;
try {
jsonResult = JSON.parse(val);
} catch (e) {
console.error(e);
}
if (jsonResult) {
setJson(jsonResult);
setTabContent(tabIndex, jsonResult);
}
}}
openrpcDocument={openrpcDocument}
openrpcMethodObject={props.openrpcMethodObject}
value={JSON.stringify(json, null, 4)}
/>
Expand Down
27 changes: 16 additions & 11 deletions src/containers/JSONRPCRequestEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,36 @@
import React, { useRef, useEffect } from "react";
import MonacoEditor from "@etclabscore/react-monaco-editor";
import * as monaco from "monaco-editor";
import { MethodObject, ContentDescriptorObject } from "@open-rpc/meta-schema";
import { MethodObject, ContentDescriptorObject, OpenRPC } from "@open-rpc/meta-schema";
import useWindowSize from "@rehooks/window-size";
import { addDiagnostics } from "@etclabscore/monaco-add-json-schema-diagnostics";
import openrpcDocumentToJSONRPCSchema from "../helpers/openrpcDocumentToJSONRPCSchema";

interface IProps {
onChange?: (newValue: any) => void;
openrpcMethodObject?: MethodObject;
openrpcDocument?: OpenRPC;
value: any;
}

const JSONRPCRequestEditor: React.FC<IProps> = (props) => {
const editorRef = useRef();
const editorRef = useRef<any>();
const windowSize = useWindowSize();

useEffect(() => {
if (editorRef !== undefined && editorRef.current !== undefined) {
(editorRef.current as any).layout();
}
}, [windowSize]);

function handleEditorDidMount(_: any, editor: any) {
editorRef.current = editor;
useEffect(() => {
if (!editorRef.current) {
return;
}
const modelName = props.openrpcMethodObject ? props.openrpcMethodObject.name : "inspector";
const modelUriString = `inmemory://${modelName}-${Math.random()}.json`;
const modelUri = monaco.Uri.parse(modelUriString);
const model = monaco.editor.createModel(props.value || "", "json", modelUri);
editor.setModel(model);
editorRef.current.setModel(model);
let schema: any = {
type: "object",
properties: {
Expand Down Expand Up @@ -87,6 +90,8 @@ const JSONRPCRequestEditor: React.FC<IProps> = (props) => {
},
},
};
} else if (props.openrpcDocument) {
schema = openrpcDocumentToJSONRPCSchema(props.openrpcDocument);
} else {
schema = {
additionalProperties: false,
Expand All @@ -102,6 +107,11 @@ const JSONRPCRequestEditor: React.FC<IProps> = (props) => {
};
}
addDiagnostics(modelUri.toString(), schema, monaco);

}, [props.openrpcDocument, props.openrpcMethodObject]);

function handleEditorDidMount(_: any, editor: any) {
editorRef.current = editor;
}

const handleChange = (ev: any, value: any) => {
Expand All @@ -113,11 +123,6 @@ const JSONRPCRequestEditor: React.FC<IProps> = (props) => {
return (
<MonacoEditor
height="93vh"
options={{
minimap: {
enabled: false,
},
}}
value={props.value}
editorDidMount={handleEditorDidMount}
language="json"
Expand Down
22 changes: 0 additions & 22 deletions src/helpers/localStorageMock.ts

This file was deleted.

Loading

0 comments on commit 1f4aae4

Please sign in to comment.