Skip to content

Commit 2d40a73

Browse files
authored
Merge pull request #504 from open-rpc/fix/add-transport-dropdown
Fix/add transport dropdown
2 parents a6ab1bf + 331232a commit 2d40a73

11 files changed

+1306
-1207
lines changed

Diff for: package-lock.json

+847-1,140
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@
4141
"react-json-view": "^1.19.1",
4242
"react-markdown": "^4.1.0",
4343
"react-split-pane": "^0.1.87",
44-
"reusable": "^1.0.0-alpha.12"
44+
"reusable": "^1.0.0-alpha.12",
45+
"use-debounce": "^5.0.4"
4546
},
4647
"scripts": {
4748
"start": "rescripts start",
@@ -70,7 +71,7 @@
7071
"monaco-editor-webpack-plugin": "^1.7.0",
7172
"react-scripts": "^3.4.3",
7273
"tslint": "^5.18.0",
73-
"typescript": "^3.5.2"
74+
"typescript": "^3.7.3"
7475
},
7576
"rescripts": [
7677
"rescript-monaco"

Diff for: src/App.tsx

+70-3
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,26 @@ import useMonacoVimMode from "./hooks/useMonacoVimMode";
2424
import { IExample } from "./ExampleDocumentsDropdown/ExampleDocumentsDropdown";
2525
import Inspector from "@open-rpc/inspector";
2626
import useInspectorActionStore from "./stores/inspectorActionStore";
27+
import { useTransport, defaultTransports, ITransport } from "./hooks/useTransport";
28+
import fetchUrlSchemaFile from "./fetchUrlSchemaFile";
29+
import queryParamsStore from "./stores/queryParamsStore";
30+
import { useDebounce } from "use-debounce";
2731

2832
const App: React.FC = () => {
29-
const [defaultValue] = useDefaultEditorValue();
33+
const [defaultValue, setDefaultValue] = useDefaultEditorValue();
3034
const [markers, setMarkers] = useState<monaco.editor.IMarker[]>([] as monaco.editor.IMarker[]);
31-
const [searchUrl, { results, error }, setSearchUrl] = searchBarStore();
35+
const [searchUrl, setSearchUrl] = searchBarStore();
36+
const [searchUrlDebounced] = useDebounce(searchUrl, 1000);
37+
const [results, setResults] = useState<any>();
38+
const [error, setError] = useState<string | undefined>();
3239
const [notification, setNotification] = useState<ISnackBarNotification | undefined>();
3340
const [UISchema, setUISchemaBySection]: [IUISchema, any] = UISchemaStore();
3441
const [editor, setEditor]: [any, Dispatch<{}>] = useState();
3542
const [horizontalSplit, privateSetHorizontalSplit] = useState(false);
3643
const [parsedSchema, setParsedSchema] = useParsedSchema(
3744
defaultValue ? JSON.parse(defaultValue) : null,
3845
);
46+
const [query] = queryParamsStore();
3947
const setHorizontalSplit = (val: boolean) => {
4048
if (editor) {
4149
setTimeout(() => {
@@ -104,7 +112,54 @@ const App: React.FC = () => {
104112
indentWidth: 2,
105113
name: false,
106114
});
115+
const [transportList, setTransportList] = useState(defaultTransports);
116+
const getQueryTransport = () => {
117+
if (!query.transport) {
118+
return transportList[0];
119+
}
120+
const queryTransport = transportList.find((item) => item.type === query.transport);
121+
return queryTransport || transportList[0];
122+
};
107123
const currentTheme = UISchema.appBar["ui:darkMode"] ? darkTheme : lightTheme;
124+
const [transport, selectedTransportType, setTransportType] = useTransport(
125+
transportList,
126+
searchUrlDebounced,
127+
getQueryTransport(),
128+
);
129+
const refreshOpenRpcDocument = async () => {
130+
// handle .json urls
131+
if (searchUrlDebounced && searchUrlDebounced.includes(".json")) {
132+
const rd = await fetchUrlSchemaFile(searchUrlDebounced);
133+
setDefaultValue(rd);
134+
return setResults(rd);
135+
}
136+
try {
137+
const d = await transport?.sendData({
138+
internalID: 999999,
139+
request: {
140+
jsonrpc: "2.0",
141+
params: [],
142+
id: 999999,
143+
method: "rpc.discover",
144+
},
145+
});
146+
const rd = JSON.stringify(d, null, 2);
147+
if (rd) {
148+
setDefaultValue(rd);
149+
setResults(rd);
150+
}
151+
} catch (e) {
152+
setError(e.message);
153+
}
154+
};
155+
156+
useEffect(() => {
157+
if (searchUrlDebounced && transport) {
158+
refreshOpenRpcDocument();
159+
}
160+
// eslint-disable-next-line react-hooks/exhaustive-deps
161+
}, [searchUrlDebounced, transport]);
162+
108163
useEffect(() => {
109164
if (inspectorContents) {
110165
setHorizontalSplit(true);
@@ -119,6 +174,17 @@ const App: React.FC = () => {
119174
uiSchema={UISchema}
120175
examples={examples as IExample[]}
121176
onExampleDocumentsDropdownChange={(example: IExample) => setSearchUrl(example.url)}
177+
selectedTransport={selectedTransportType}
178+
transportList={transportList}
179+
onTransportChange={(changedTransport) => setTransportType(changedTransport)}
180+
onTransportAdd={(addedTransport: ITransport) => {
181+
setTransportList((oldList) => {
182+
return [
183+
...oldList,
184+
addedTransport,
185+
];
186+
});
187+
}}
122188
onSplitViewChange={(value) => {
123189
setUISchemaBySection({
124190
value,
@@ -147,8 +213,9 @@ const App: React.FC = () => {
147213
right={
148214
<>
149215
<Inspector hideToggleTheme={true} url={
150-
searchUrl && searchUrl.includes(".json") ? null : searchUrl
216+
searchUrlDebounced && searchUrlDebounced.includes(".json") ? null : searchUrlDebounced
151217
}
218+
transport={selectedTransportType.type !== "plugin" ? selectedTransportType.type : undefined}
152219
request={inspectorContents && inspectorContents.request}
153220
openrpcDocument={parsedSchema}
154221
/>

Diff for: src/AppBar/AppBar.tsx

+22-5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import EditIcon from "@material-ui/icons/Edit";
1919
import { IUISchema } from "../UISchema";
2020
import SearchBar from "../SearchBar/SearchBar";
2121
import ExampleDocumentsDropdown, { IExample } from "../ExampleDocumentsDropdown/ExampleDocumentsDropdown";
22+
import { ITransport } from "../hooks/useTransport";
23+
import TransportDropdown from "../components/TransportDropdown";
2224

2325
const styles = (theme: Theme) => ({
2426
title: {
@@ -31,11 +33,15 @@ const styles = (theme: Theme) => ({
3133
interface IProps extends WithStyles<typeof styles> {
3234
uiSchema?: IUISchema;
3335
examples?: IExample[];
36+
transportList?: ITransport[];
37+
selectedTransport?: ITransport;
3438
searchBarUrl?: string | undefined;
3539
onChangeUrl?: any;
3640
onDarkModeChange?: any;
3741
onSplitViewChange?: (split: boolean) => any;
3842
onExampleDocumentsDropdownChange?: (example: IExample) => any;
43+
onTransportAdd: (transport: ITransport) => any;
44+
onTransportChange: (transport: ITransport) => any;
3945
}
4046

4147
class ApplicationBar extends Component<IProps> {
@@ -52,7 +58,7 @@ class ApplicationBar extends Component<IProps> {
5258
<AppBar position="fixed" color="default" elevation={0} className={classes.appBar}>
5359
<Toolbar>
5460
<Grid alignItems="center" container>
55-
<Grid item xs={6} sm={6} md={3} direction="row" container>
61+
<Grid item xs={6} sm={6} md={2} direction="row" container>
5662
{this.props.uiSchema && this.props.uiSchema.appBar && this.props.uiSchema.appBar["ui:logoUrl"] &&
5763
<Grid>
5864
<img
@@ -68,8 +74,19 @@ class ApplicationBar extends Component<IProps> {
6874
</Grid>
6975
</Grid>
7076
<Hidden smDown>
71-
<Grid item container justify="center" alignItems="center" sm={6} >
72-
<Grid item sm={9}>
77+
<Grid item container justify="center" alignItems="center" sm={8} >
78+
<Grid item>
79+
<TransportDropdown
80+
transports={this.props.transportList}
81+
onAddTransport={this.props.onTransportAdd}
82+
selectedTransport={this.props.selectedTransport}
83+
onChange={this.props.onTransportChange}
84+
style={{
85+
marginLeft: "10px",
86+
}}
87+
/>
88+
</Grid>
89+
<Grid item sm={6}>
7390
{this.props.uiSchema && this.props.uiSchema.appBar && this.props.uiSchema.appBar["ui:input"] &&
7491
<Paper style={{
7592
background: "rgba(0, 0, 0, 0.1)",
@@ -85,12 +102,12 @@ class ApplicationBar extends Component<IProps> {
85102
}
86103
</Grid>
87104
{this.props.uiSchema && this.props.uiSchema.appBar &&
88-
this.props.uiSchema.appBar["ui:examplesDropdown"] &&
105+
this.props.uiSchema.appBar["ui:examplesDropdown"] &&
89106
<ExampleDocumentsDropdown examples={examples} onChange={onExampleDocumentsDropdownChange} />
90107
}
91108
</Grid>
92109
</Hidden>
93-
<Grid item xs={6} sm={6} md={3} container justify="flex-end" alignItems="center">
110+
<Grid item xs={6} sm={6} md={2} container justify="flex-end" alignItems="center">
94111
{uiSchema && uiSchema.appBar["ui:splitView"] ?
95112
<Tooltip title={"Full Screen"}>
96113
<IconButton onClick={() => {

Diff for: src/components/TransportDropdown.tsx

+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import React, { useState, ChangeEvent } from "react";
2+
import { Button, Menu, MenuItem, Typography, Dialog, Container, Grid, InputBase } from "@material-ui/core";
3+
import { CSSProperties } from "@material-ui/core/styles/withStyles";
4+
import PlusIcon from "@material-ui/icons/Add";
5+
import DropdownArrowIcon from "@material-ui/icons/ArrowDropDown";
6+
import { ITransport } from "../hooks/useTransport";
7+
8+
interface IProps {
9+
transports?: ITransport[];
10+
selectedTransport?: ITransport;
11+
onChange?: (changedTransport: ITransport) => void;
12+
onAddTransport?: (addedTransport: ITransport) => void;
13+
style?: CSSProperties;
14+
}
15+
16+
const TransportDropdown: React.FC<IProps> = ({ selectedTransport, transports, onChange, style, onAddTransport }) => {
17+
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
18+
setAnchorEl(event.currentTarget);
19+
};
20+
const handleClose = () => {
21+
setAnchorEl(null);
22+
};
23+
const handleMenuItemClick = (transport: ITransport) => {
24+
setAnchorEl(null);
25+
if (onChange) {
26+
onChange(transport);
27+
}
28+
};
29+
30+
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
31+
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
32+
33+
const [selectedCustomTransport, setSelectedCustomTransport] = useState<ITransport | undefined>();
34+
const [customTransportName, setCustomTransportName] = useState<string | undefined>();
35+
const [customTransportUri, setCustomTransportUri] = useState<string | undefined>();
36+
37+
const [dialogMenuAnchorEl, setDialogMenuAnchorEl] = useState<null | HTMLElement>(null);
38+
39+
const handleDialogAnchorClose = () => {
40+
setDialogMenuAnchorEl(null);
41+
};
42+
const handleDialogCustomTransportClick = (event: React.MouseEvent<HTMLButtonElement>) => {
43+
setDialogMenuAnchorEl(event.currentTarget);
44+
};
45+
46+
const handleCustomTransportDialogMenuItemClick = (transport: ITransport) => {
47+
setDialogMenuAnchorEl(null);
48+
setSelectedCustomTransport(transport);
49+
};
50+
51+
const handleSubmitCustomTransport = () => {
52+
setDialogMenuAnchorEl(null);
53+
if (selectedCustomTransport && customTransportName && customTransportUri) {
54+
const t: ITransport = {
55+
type: "plugin",
56+
transport: selectedCustomTransport,
57+
name: customTransportName,
58+
uri: customTransportUri,
59+
};
60+
if (onAddTransport) {
61+
onAddTransport(t);
62+
}
63+
setDialogOpen(false);
64+
}
65+
};
66+
return (
67+
<div style={style}>
68+
<Dialog onClose={() => setDialogOpen(false)} aria-labelledby="simple-dialog-title" open={dialogOpen}>
69+
<Container maxWidth="sm">
70+
<Grid
71+
container
72+
justify="space-between"
73+
alignItems="center"
74+
style={{ padding: "30px", paddingTop: "10px", paddingBottom: "10px", marginTop: "10px" }}>
75+
<Typography variant="h6">Custom Transport Plugin</Typography>
76+
<Typography variant="caption" gutterBottom>
77+
Transport plugins are created by implementing the "connect",
78+
"sendData", and "close" methods over JSON-RPC.
79+
</Typography>
80+
<Grid container direction="column" spacing={1}>
81+
<Grid item>
82+
<InputBase placeholder="Plugin Name"
83+
onChange={
84+
(event: ChangeEvent<HTMLInputElement>) => {
85+
setCustomTransportName(event.target.value);
86+
}
87+
}
88+
style={{
89+
display: "block",
90+
background: "rgba(0,0,0,0.1)",
91+
borderRadius: "4px",
92+
padding: "0px 10px",
93+
marginRight: "5px",
94+
}}
95+
/>
96+
</Grid>
97+
<Grid item>
98+
<InputBase placeholder="Plugin URI"
99+
onChange={
100+
(event: ChangeEvent<HTMLInputElement>) => {
101+
setCustomTransportUri(event.target.value);
102+
}
103+
}
104+
style={{
105+
display: "block",
106+
background: "rgba(0,0,0,0.1)",
107+
borderRadius: "4px",
108+
padding: "0px 10px",
109+
marginRight: "5px",
110+
}}
111+
/>
112+
</Grid>
113+
<Grid item>
114+
<Button
115+
variant="outlined"
116+
onClick={handleDialogCustomTransportClick}>
117+
{selectedCustomTransport ? selectedCustomTransport.name : "Select A Transport"}
118+
</Button>
119+
</Grid>
120+
</Grid>
121+
<Menu
122+
id="transport-menu"
123+
anchorEl={dialogMenuAnchorEl}
124+
keepMounted
125+
open={Boolean(dialogMenuAnchorEl)}
126+
onClose={handleDialogAnchorClose}
127+
>
128+
{transports && transports.filter((value) => value.type !== "plugin").map((transport, i) => (
129+
<MenuItem
130+
onClick={() => handleCustomTransportDialogMenuItemClick(transport)}
131+
>{transport.name}</MenuItem>
132+
))}
133+
</Menu>
134+
<Button
135+
style={{ marginTop: "10px", marginBottom: "10px" }}
136+
onClick={handleSubmitCustomTransport}
137+
disabled={!customTransportName || !customTransportUri || !selectedCustomTransport}
138+
variant="contained">
139+
Add Transport
140+
</Button>
141+
</Grid>
142+
</Container>
143+
</Dialog>
144+
<Button
145+
style={{
146+
marginRight: "10px",
147+
marginLeft: "5px",
148+
}}
149+
variant="outlined"
150+
onClick={handleClick} endIcon={<DropdownArrowIcon />}
151+
>{selectedTransport && selectedTransport.name}</Button>
152+
<Menu
153+
id="transport-menu"
154+
anchorEl={anchorEl}
155+
keepMounted
156+
open={Boolean(anchorEl)}
157+
onClose={handleClose}
158+
>
159+
{transports && transports.map((transport, i) => (
160+
<MenuItem onClick={() => handleMenuItemClick(transport)}>{transport.name}</MenuItem>
161+
))}
162+
<MenuItem onClick={() => setDialogOpen(true)}>
163+
<PlusIcon style={{ marginRight: "5px" }} /><Typography variant="caption">Add Transport</Typography>
164+
</MenuItem>
165+
</Menu>
166+
</div>
167+
);
168+
};
169+
170+
export default TransportDropdown;

Diff for: src/fetchSchemaFromRpcDiscover.tsx

-18
This file was deleted.

0 commit comments

Comments
 (0)