Skip to content

Commit 1f4aae4

Browse files
committed
feat: add tabs and openrpdocument autocomplete
1 parent 933e143 commit 1f4aae4

8 files changed

+408
-57
lines changed

package-lock.json

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"react-json-view": "^1.19.1",
6464
"react-split-pane": "^0.1.87",
6565
"semantic-release": "^15.13.21",
66-
"use-dark-mode": "^2.3.1"
66+
"use-dark-mode": "^2.3.1",
67+
"use-debounce": "^3.3.0"
6768
}
6869
}

src/containers/App.tsx

+3-13
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,15 @@
11
import React, { useEffect } from "react";
22
import { CssBaseline } from "@material-ui/core";
33
import { MuiThemeProvider } from "@material-ui/core";
4+
45
import { lightTheme, darkTheme } from "../themes/openrpcTheme";
56
import useDarkMode from "use-dark-mode";
67
import Inspector from "./Inspector";
78
import useQueryParams from "../hooks/useQueryParams";
89
import * as monaco from "monaco-editor";
9-
import localStorageMock from "../helpers/localStorageMock";
10-
11-
let localStorageEnabled = true;
12-
try {
13-
window.localStorage.setItem("xyz-test", "true");
14-
} catch (e) {
15-
localStorageEnabled = false;
16-
console.error(e);
17-
}
18-
19-
// mock storageProvider for when localStorage is not available via chrome/brave settings
20-
const darkModeOptions = localStorageEnabled ? undefined : localStorageMock;
2110

2211
const App: React.FC = () => {
23-
const darkMode = useDarkMode(undefined, darkModeOptions);
12+
const darkMode = useDarkMode();
2413
const [query] = useQueryParams();
2514
const theme = darkMode.value ? darkTheme : lightTheme;
2615
useEffect(() => {
@@ -31,6 +20,7 @@ const App: React.FC = () => {
3120
return (
3221
<MuiThemeProvider theme={theme}>
3322
<CssBaseline />
23+
3424
<Inspector
3525
onToggleDarkMode={darkMode.toggle}
3626
darkMode={darkMode.value}

src/containers/Inspector.tsx

+134-10
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
import React, { useState, useEffect, ChangeEvent, Dispatch, useRef } from "react";
22
import SplitPane from "react-split-pane";
3+
import _ from "lodash";
34
import JSONRPCRequestEditor from "./JSONRPCRequestEditor";
45
import PlayCircle from "@material-ui/icons/PlayCircleFilled";
5-
import { IconButton, AppBar, Toolbar, Typography, Button, InputBase } from "@material-ui/core";
6+
import CloseIcon from "@material-ui/icons/Close";
7+
import PlusIcon from "@material-ui/icons/Add";
8+
import { IconButton, AppBar, Toolbar, Typography, Button, InputBase, Tab, Tabs } from "@material-ui/core";
69
import { Client, RequestManager, HTTPTransport, WebSocketTransport } from "@open-rpc/client-js";
710
import Brightness3Icon from "@material-ui/icons/Brightness3";
811
import WbSunnyIcon from "@material-ui/icons/WbSunny";
912
import { JSONRPCError } from "@open-rpc/client-js/build/Error";
1013
import { MethodObject } from "@open-rpc/meta-schema";
1114
import MonacoEditor from "@etclabscore/react-monaco-editor";
15+
import useTabs from "../hooks/useTabs";
16+
import { useDebounce } from "use-debounce";
1217

1318
const errorToJSON = (error: JSONRPCError | undefined): any => {
1419
if (!error) {
@@ -48,7 +53,6 @@ const useClient = (url: string): [Client, JSONRPCError | undefined, Dispatch<JSO
4853
setClient(c);
4954
c.onError((e) => {
5055
console.log("onError", e); //tslint:disable-line
51-
setError(e);
5256
});
5357
} catch (e) {
5458
setError(e);
@@ -67,8 +71,29 @@ function useCounter(defaultValue: number): [number, () => void] {
6771
return [counter, incrementCounter];
6872
}
6973

74+
const emptyJSONRPC = {
75+
jsonrpc: "2.0",
76+
method: "",
77+
params: [],
78+
id: "0",
79+
};
80+
7081
const Inspector: React.FC<IProps> = (props) => {
82+
const {
83+
setTabContent,
84+
setTabEditing,
85+
setTabIndex,
86+
tabs,
87+
setTabs,
88+
handleClose,
89+
tabIndex,
90+
setTabOpenRPCDocument,
91+
setTabUrl,
92+
handleLabelChange,
93+
setTabResults,
94+
} = useTabs();
7195
const [id, incrementId] = useCounter(0);
96+
const [openrpcDocument, setOpenRpcDocument] = useState();
7297
const [json, setJson] = useState(props.request || {
7398
jsonrpc: "2.0",
7499
method: "",
@@ -78,6 +103,7 @@ const Inspector: React.FC<IProps> = (props) => {
78103
const editorRef = useRef();
79104
const [results, setResults] = useState();
80105
const [url, setUrl] = useState(props.url || "");
106+
const [debouncedUrl] = useDebounce(url, 1000);
81107
const [client, error, setError] = useClient(url);
82108
useEffect(() => {
83109
if (props.openrpcMethodObject) {
@@ -92,18 +118,26 @@ const Inspector: React.FC<IProps> = (props) => {
92118
}, []);
93119
useEffect(() => {
94120
if (json) {
95-
setJson({
121+
const jsonResult = {
96122
...json,
97123
jsonrpc: "2.0",
98124
id: id.toString(),
99-
});
125+
};
126+
setJson(jsonResult);
100127
}
101128
// eslint-disable-next-line react-hooks/exhaustive-deps
102129
}, [id]);
103130

131+
useEffect(() => {
132+
if (json) {
133+
setTabContent(tabIndex, json);
134+
}
135+
}, [json]);
136+
104137
useEffect(() => {
105138
if (props.url) {
106139
setUrl(props.url);
140+
setTabUrl(tabIndex, props.url);
107141
}
108142
}, [props.url]);
109143

@@ -113,19 +147,22 @@ const Inspector: React.FC<IProps> = (props) => {
113147
incrementId();
114148
try {
115149
const result = await client.request(json.method, json.params);
116-
setResults({ jsonrpc: "2.0", result, id });
150+
const r = { jsonrpc: "2.0", result, id };
151+
setResults(r);
152+
setTabResults(tabIndex, r);
117153
} catch (e) {
118-
setError(e);
154+
setResults(e);
155+
setTabResults(tabIndex, e);
119156
}
120157
}
121158
};
122-
function handleResponseEditorDidMount(_: any, editor: any) {
159+
function handleResponseEditorDidMount(__: any, editor: any) {
123160
editorRef.current = editor;
124161
}
125162

126163
const clear = () => {
127164
setResults(undefined);
128-
setError(undefined);
165+
setTabResults(tabIndex, undefined);
129166
};
130167

131168
const handleClearButton = () => {
@@ -137,9 +174,83 @@ const Inspector: React.FC<IProps> = (props) => {
137174
props.onToggleDarkMode();
138175
}
139176
};
177+
const refreshOpenRpcDocument = async () => {
178+
if (url) {
179+
try {
180+
const d = await client.request("rpc.discover", []);
181+
setOpenRpcDocument(d);
182+
setTabOpenRPCDocument(tabIndex, d);
183+
} catch (e) {
184+
setOpenRpcDocument(undefined);
185+
setTabOpenRPCDocument(tabIndex, undefined);
186+
}
187+
}
188+
};
189+
190+
useEffect(() => {
191+
refreshOpenRpcDocument();
192+
}, [debouncedUrl]);
193+
194+
useEffect(() => {
195+
if (tabs[tabIndex]) {
196+
setJson(tabs[tabIndex].content);
197+
setUrl(tabs[tabIndex].url || "");
198+
setOpenRpcDocument(tabs[tabIndex].openrpcDocument);
199+
setResults(tabs[tabIndex].results);
200+
}
201+
}, [tabIndex]);
202+
203+
const handleTabIndexChange = (event: React.ChangeEvent<{}>, newValue: number) => {
204+
setTabIndex(newValue);
205+
};
140206

141207
return (
142208
<>
209+
<div style={{ position: "relative" }}>
210+
<Tabs
211+
style={{ background: "transparent" }}
212+
value={tabIndex}
213+
variant="scrollable"
214+
indicatorColor="primary"
215+
onChange={handleTabIndexChange}
216+
>
217+
{tabs.map((tab, index) => (
218+
<Tab disableRipple style={{
219+
border: "none",
220+
outline: "none",
221+
userSelect: "none",
222+
}} onDoubleClick={() => setTabEditing(tab, true)} label={
223+
<div style={{ userSelect: "none" }}>
224+
{tab.editing
225+
? <InputBase
226+
value={tab.name}
227+
onChange={(ev) => handleLabelChange(ev, tab)}
228+
onBlur={() => setTabEditing(tab, false)}
229+
autoFocus
230+
style={{ maxWidth: "80px", marginRight: "25px" }}
231+
/>
232+
: <Typography style={{ display: "inline", textTransform: "none", marginRight: "25px" }} variant="body1" >{tab.name}</Typography>
233+
}
234+
{tabIndex === index
235+
?
236+
<IconButton onClick={
237+
(ev) => handleClose(ev, index)
238+
} style={{ position: "absolute", right: "10px", top: "25%" }} size="small">
239+
<CloseIcon />
240+
</IconButton>
241+
: null
242+
}
243+
</div>
244+
}></Tab>
245+
))}
246+
<Tab disableRipple style={{ minWidth: "50px" }} label={
247+
<IconButton onClick={() => setTabs([...tabs, { name: "New Tab", content: { ...emptyJSONRPC }, url: "" }])}>
248+
<PlusIcon scale={0.5} />
249+
</IconButton>
250+
}>
251+
</Tab>
252+
</Tabs>
253+
</div>
143254
<AppBar elevation={0} position="static">
144255
<Toolbar>
145256
<img
@@ -156,7 +267,10 @@ const Inspector: React.FC<IProps> = (props) => {
156267
value={url}
157268
placeholder="Enter a JSON-RPC server URL"
158269
onChange={
159-
(event: ChangeEvent<HTMLInputElement>) => setUrl(event.target.value)
270+
(event: ChangeEvent<HTMLInputElement>) => {
271+
setUrl(event.target.value);
272+
setTabUrl(tabIndex, event.target.value);
273+
}
160274
}
161275
fullWidth
162276
style={{ background: "rgba(0,0,0,0.1)", borderRadius: "4px", padding: "0px 10px", marginRight: "5px" }}
@@ -183,8 +297,18 @@ const Inspector: React.FC<IProps> = (props) => {
183297
}}>
184298
<JSONRPCRequestEditor
185299
onChange={(val) => {
186-
setJson(JSON.parse(val));
300+
let jsonResult;
301+
try {
302+
jsonResult = JSON.parse(val);
303+
} catch (e) {
304+
console.error(e);
305+
}
306+
if (jsonResult) {
307+
setJson(jsonResult);
308+
setTabContent(tabIndex, jsonResult);
309+
}
187310
}}
311+
openrpcDocument={openrpcDocument}
188312
openrpcMethodObject={props.openrpcMethodObject}
189313
value={JSON.stringify(json, null, 4)}
190314
/>

src/containers/JSONRPCRequestEditor.tsx

+16-11
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,36 @@
11
import React, { useRef, useEffect } from "react";
22
import MonacoEditor from "@etclabscore/react-monaco-editor";
33
import * as monaco from "monaco-editor";
4-
import { MethodObject, ContentDescriptorObject } from "@open-rpc/meta-schema";
4+
import { MethodObject, ContentDescriptorObject, OpenRPC } from "@open-rpc/meta-schema";
55
import useWindowSize from "@rehooks/window-size";
66
import { addDiagnostics } from "@etclabscore/monaco-add-json-schema-diagnostics";
7+
import openrpcDocumentToJSONRPCSchema from "../helpers/openrpcDocumentToJSONRPCSchema";
78

89
interface IProps {
910
onChange?: (newValue: any) => void;
1011
openrpcMethodObject?: MethodObject;
12+
openrpcDocument?: OpenRPC;
1113
value: any;
1214
}
1315

1416
const JSONRPCRequestEditor: React.FC<IProps> = (props) => {
15-
const editorRef = useRef();
17+
const editorRef = useRef<any>();
1618
const windowSize = useWindowSize();
17-
1819
useEffect(() => {
1920
if (editorRef !== undefined && editorRef.current !== undefined) {
2021
(editorRef.current as any).layout();
2122
}
2223
}, [windowSize]);
2324

24-
function handleEditorDidMount(_: any, editor: any) {
25-
editorRef.current = editor;
25+
useEffect(() => {
26+
if (!editorRef.current) {
27+
return;
28+
}
2629
const modelName = props.openrpcMethodObject ? props.openrpcMethodObject.name : "inspector";
2730
const modelUriString = `inmemory://${modelName}-${Math.random()}.json`;
2831
const modelUri = monaco.Uri.parse(modelUriString);
2932
const model = monaco.editor.createModel(props.value || "", "json", modelUri);
30-
editor.setModel(model);
33+
editorRef.current.setModel(model);
3134
let schema: any = {
3235
type: "object",
3336
properties: {
@@ -87,6 +90,8 @@ const JSONRPCRequestEditor: React.FC<IProps> = (props) => {
8790
},
8891
},
8992
};
93+
} else if (props.openrpcDocument) {
94+
schema = openrpcDocumentToJSONRPCSchema(props.openrpcDocument);
9095
} else {
9196
schema = {
9297
additionalProperties: false,
@@ -102,6 +107,11 @@ const JSONRPCRequestEditor: React.FC<IProps> = (props) => {
102107
};
103108
}
104109
addDiagnostics(modelUri.toString(), schema, monaco);
110+
111+
}, [props.openrpcDocument, props.openrpcMethodObject]);
112+
113+
function handleEditorDidMount(_: any, editor: any) {
114+
editorRef.current = editor;
105115
}
106116

107117
const handleChange = (ev: any, value: any) => {
@@ -113,11 +123,6 @@ const JSONRPCRequestEditor: React.FC<IProps> = (props) => {
113123
return (
114124
<MonacoEditor
115125
height="93vh"
116-
options={{
117-
minimap: {
118-
enabled: false,
119-
},
120-
}}
121126
value={props.value}
122127
editorDidMount={handleEditorDidMount}
123128
language="json"

src/helpers/localStorageMock.ts

-22
This file was deleted.

0 commit comments

Comments
 (0)