@canmingir/link 1.2.23 → 1.2.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/package.json +3 -1
  2. package/src/Platform.jsx +10 -14
  3. package/src/context/Context.js +98 -0
  4. package/src/context/reducer.js +590 -10
  5. package/src/lib/APIDialogAction/APIDialogAction.jsx +109 -0
  6. package/src/lib/APIDialogAction/index.js +1 -0
  7. package/src/lib/APIDialogAction/styles.js +6 -0
  8. package/src/lib/APIParams/APIParams.jsx +57 -0
  9. package/src/lib/APIParams/index.js +1 -0
  10. package/src/lib/APIPath/APIPath.jsx +82 -0
  11. package/src/lib/APIPath/index.js +1 -0
  12. package/src/lib/APIPath/styles.js +19 -0
  13. package/src/lib/APITree/APITree.jsx +409 -0
  14. package/src/lib/APITree/Arrow.jsx +21 -0
  15. package/src/lib/APITree/DeleteMethodDialog.jsx +41 -0
  16. package/src/lib/APITree/index.js +1 -0
  17. package/src/lib/APITree/styles.js +19 -0
  18. package/src/lib/APITypes/APITypes.jsx +141 -0
  19. package/src/lib/APITypes/TypeEditor.jsx +46 -0
  20. package/src/lib/APITypes/TypeList.jsx +180 -0
  21. package/src/lib/APITypes/index.js +1 -0
  22. package/src/lib/BlankTreeMessage/BlankTreeMessage.jsx +39 -0
  23. package/src/lib/BlankTreeMessage/index.js +1 -0
  24. package/src/lib/DialogTootip/DialogTooltip.jsx +67 -0
  25. package/src/lib/DialogTootip/index.js +1 -0
  26. package/src/lib/DialogTootip/styles.js +9 -0
  27. package/src/lib/NewApiBody/NewAPIBody.jsx +97 -0
  28. package/src/lib/NewApiBody/ParamView.jsx +38 -0
  29. package/src/lib/NucDialog/NucDialog.jsx +108 -0
  30. package/src/lib/NucDialog/index.js +1 -0
  31. package/src/lib/ParamTable/ParamTable.jsx +133 -0
  32. package/src/lib/ParamTable/TypeMenu.jsx +102 -0
  33. package/src/lib/ParamTable/defaults.js +47 -0
  34. package/src/lib/ParamTable/index.js +1 -0
  35. package/src/lib/ParamTable/styles.js +12 -0
  36. package/src/lib/ResourceMenu/AlertMassage.jsx +28 -0
  37. package/src/lib/ResourceMenu/DeleteResourceDialog.jsx +60 -0
  38. package/src/lib/ResourceMenu/ResourceMenu.jsx +156 -0
  39. package/src/lib/ResourceMenu/index.js +1 -0
  40. package/src/lib/ResourceMenu/styles.js +5 -0
  41. package/src/lib/Schema/Schema.jsx +204 -0
  42. package/src/lib/Schema/index.js +1 -0
  43. package/src/lib/SchemaEditor/SchemaEditor.jsx +258 -0
  44. package/src/lib/SchemaEditor/SchemaEditor.test.js +193 -0
  45. package/src/lib/SchemaEditor/SchemaPropertyEditor.jsx +135 -0
  46. package/src/lib/SchemaEditor/SchemaUtils.js +152 -0
  47. package/src/lib/SchemaEditor/index.js +1 -0
  48. package/src/lib/ToggleableMenu/ToggleableMenu.jsx +35 -0
  49. package/src/lib/ToggleableMenu/index.js +1 -0
  50. package/src/lib/index.js +14 -0
  51. package/src/pages/Callback.jsx +2 -4
  52. package/src/stories/APITree.stories.jsx +429 -0
  53. package/src/stories/FlowChart.stories.jsx +1 -1
  54. package/src/templates/ActionTemplate.js +24 -0
  55. package/src/widgets/SettingsDialog.jsx +33 -11
@@ -0,0 +1,109 @@
1
+ import DeleteIcon from "@mui/icons-material/Delete";
2
+ import DialogTooltip from "../DialogTootip/DialogTooltip";
3
+ import SaveIcon from "@mui/icons-material/Save";
4
+
5
+ import { Box, Button, ToggleButton, ToggleButtonGroup } from "@mui/material";
6
+ import { useEffect, useState } from "react";
7
+
8
+ function APIDialogAction({
9
+ view,
10
+ setApiDialogView,
11
+ saveApiDialog,
12
+ saveDisable,
13
+ deleteDisable,
14
+ deleteMethod,
15
+ }) {
16
+ const [alignment, setAlignment] = useState();
17
+ const [openToolTip, setOpenToolTip] = useState(false);
18
+
19
+ const handleTooltipClose = () => {
20
+ setOpenToolTip(false);
21
+ };
22
+
23
+ const handleTooltipOpen = () => {
24
+ setOpenToolTip(true);
25
+ };
26
+
27
+ useEffect(() => {
28
+ setAlignment(view);
29
+ }, [view]);
30
+
31
+ return (
32
+ <Box sx={{ display: "flex", justifyContent: "space-between" }}>
33
+ <Box>
34
+ <ToggleButtonGroup
35
+ value={alignment}
36
+ exclusive
37
+ size={"small"}
38
+ onChange={(event, newAlignment) => {
39
+ if (!newAlignment) return;
40
+ setAlignment(newAlignment);
41
+ setApiDialogView(newAlignment);
42
+ }}
43
+ sx={{
44
+ "& .MuiToggleButton-root": {
45
+ borderRadius: 1,
46
+ textTransform: "none",
47
+ fontWeight: "normal",
48
+ },
49
+ }}
50
+ >
51
+ <ToggleButton value={"PARAMS"} data-cy="params-toggle">
52
+ PARAMS
53
+ </ToggleButton>
54
+ <ToggleButton value={"BODY"} data-cy="body-toggle">
55
+ BODY
56
+ </ToggleButton>
57
+ </ToggleButtonGroup>
58
+ </Box>
59
+ <Box sx={{ display: "flex", gap: 1 }}>
60
+ <DialogTooltip
61
+ placement="top"
62
+ open={openToolTip}
63
+ title={<b>Delete method</b>}
64
+ message={
65
+ <>
66
+ This method will be <b>deleted.</b>
67
+ <br /> Do you want to continue?
68
+ </>
69
+ }
70
+ footer={
71
+ <Button
72
+ variant="contained"
73
+ color="error"
74
+ onClick={deleteMethod}
75
+ startIcon={<DeleteIcon />}
76
+ data-cy="delete-api-button-yes"
77
+ >
78
+ Delete
79
+ </Button>
80
+ }
81
+ handleTooltipClose={handleTooltipClose}
82
+ >
83
+ <Button
84
+ variant="outlined"
85
+ color="error"
86
+ onClick={handleTooltipOpen}
87
+ disabled={deleteDisable}
88
+ startIcon={<DeleteIcon />}
89
+ data-cy="delete-api-button"
90
+ >
91
+ Delete
92
+ </Button>
93
+ </DialogTooltip>
94
+ <Button
95
+ variant="contained"
96
+ color="primary"
97
+ onClick={saveApiDialog}
98
+ disabled={saveDisable}
99
+ startIcon={<SaveIcon />}
100
+ data-cy="save-api-button"
101
+ >
102
+ Save
103
+ </Button>
104
+ </Box>
105
+ </Box>
106
+ );
107
+ }
108
+
109
+ export default APIDialogAction;
@@ -0,0 +1 @@
1
+ export { default } from "./APIDialogAction";
@@ -0,0 +1,6 @@
1
+ const styles = {
2
+ root: { justifyContent: "space-between" },
3
+ firstElement: { width: 200 },
4
+ };
5
+
6
+ export default styles;
@@ -0,0 +1,57 @@
1
+ import AddIcon from "@mui/icons-material/Add";
2
+ import ParamTable from "../ParamTable";
3
+
4
+ import { Box, Fab } from "@mui/material";
5
+ import { useEffect, useState } from "react";
6
+
7
+ const APIParams = ({ types, paramsRef, addParams }) => {
8
+ const [params, setParams] = useState(paramsRef.current);
9
+
10
+ useEffect(() => {
11
+ setParams(paramsRef.current);
12
+ }, [paramsRef]);
13
+
14
+ useEffect(() => {
15
+ paramsRef.current = params;
16
+ }, [params, paramsRef]);
17
+
18
+ const handleAddParams = () => {
19
+ const id = Date.now().toString();
20
+ const newParam = {
21
+ id,
22
+ in: "query",
23
+ type: "string",
24
+ required: true,
25
+ };
26
+ setParams((prevParams) => [...prevParams, newParam]);
27
+ };
28
+
29
+ if (addParams) {
30
+ addParams.current = handleAddParams;
31
+ }
32
+
33
+ return (
34
+ <Box
35
+ sx={{
36
+ display: "flex",
37
+ flexDirection: "column",
38
+ height: "85%",
39
+ p: 2,
40
+ }}
41
+ data-cy="api-params"
42
+ >
43
+ <ParamTable types={types} params={params} setParams={setParams} />
44
+ <Box sx={{ display: "flex", justifyContent: "flex-end" }}>
45
+ <Fab
46
+ size={"small"}
47
+ onClick={handleAddParams}
48
+ data-cy="add-param-button"
49
+ >
50
+ <AddIcon />
51
+ </Fab>
52
+ </Box>
53
+ </Box>
54
+ );
55
+ };
56
+
57
+ export default APIParams;
@@ -0,0 +1 @@
1
+ export { default } from "./APIParams";
@@ -0,0 +1,82 @@
1
+ import LanguageIcon from "@mui/icons-material/Language";
2
+ import styles from "./styles";
3
+
4
+ import {
5
+ Box,
6
+ Button,
7
+ Grid,
8
+ MenuItem,
9
+ Select,
10
+ TextField,
11
+ Typography,
12
+ } from "@mui/material";
13
+ import React, { useEffect, useState } from "react";
14
+
15
+ const APIPath = ({
16
+ method,
17
+ path,
18
+ methodRef,
19
+ pathRef,
20
+ onTypesButtonClick,
21
+ allowedMethods,
22
+ isMethodDisabled,
23
+ isPathDisabled,
24
+ validatePath,
25
+ }) => {
26
+ const [selectedMethod, setSelectedMethod] = useState(
27
+ allowedMethods.includes(method) ? method : allowedMethods[0] || ""
28
+ );
29
+ const [selectedPath, setSelectedPath] = useState("");
30
+
31
+ useEffect(() => {
32
+ methodRef.current = selectedMethod;
33
+ pathRef.current = path + (path !== "/" ? "/" : "") + selectedPath;
34
+ validatePath(selectedPath);
35
+ }, [selectedMethod, selectedPath, methodRef, pathRef, path, validatePath]);
36
+
37
+ return (
38
+ <Grid container sx={styles.root} data-cy="api-path">
39
+ <Grid sx={styles.firstElement} />
40
+ <Grid item>
41
+ <Grid container item sx={styles.content}>
42
+ {isMethodDisabled ? (
43
+ <Typography data-cy="method-text">{method}</Typography>
44
+ ) : (
45
+ <Select
46
+ value={selectedMethod}
47
+ onChange={(e) => setSelectedMethod(e.target.value)}
48
+ data-cy="method-select"
49
+ >
50
+ {allowedMethods.map((method) => (
51
+ <MenuItem
52
+ key={method}
53
+ value={method}
54
+ data-cy={`method-menuitem-${method}`}
55
+ >
56
+ {method}
57
+ </MenuItem>
58
+ ))}
59
+ </Select>
60
+ )}
61
+ <Box component="span" sx={styles.text}></Box>
62
+ <Typography data-cy="path-text">
63
+ {path} {path !== "/" ? "/" : ""}
64
+ </Typography>
65
+ {!isPathDisabled && (
66
+ <TextField
67
+ value={selectedPath}
68
+ onChange={(e) => setSelectedPath(e.target.value)}
69
+ data-cy="path-input"
70
+ />
71
+ )}
72
+ </Grid>
73
+ </Grid>
74
+ <Button onClick={onTypesButtonClick} data-cy="types-button">
75
+ <LanguageIcon sx={styles.icon} />
76
+ Types
77
+ </Button>
78
+ </Grid>
79
+ );
80
+ };
81
+
82
+ export default APIPath;
@@ -0,0 +1 @@
1
+ export { default } from "./APIPath";
@@ -0,0 +1,19 @@
1
+ const styles = {
2
+ root: {
3
+ justifyContent: "space-between",
4
+ },
5
+ firstElement: { width: 50 },
6
+ content: {
7
+ justifyContent: "center",
8
+ alignItems: "center",
9
+ },
10
+ text: {
11
+ fontSize: 16,
12
+ ml: 2,
13
+ mr: 1,
14
+ },
15
+ textField: { width: 75 },
16
+ icon: { fill: "#5d5d5d", mr: 1 },
17
+ };
18
+
19
+ export default styles;
@@ -0,0 +1,409 @@
1
+ import AddIcon from "@mui/icons-material/Add";
2
+ import ArrowIcon from "./Arrow";
3
+ import BlankTreeMessage from "../BlankTreeMessage/BlankTreeMessage";
4
+ import DeleteIcon from "@mui/icons-material/Delete";
5
+ import DeleteMethodDialog from "./DeleteMethodDialog";
6
+ import EditIcon from "@mui/icons-material/Edit";
7
+ import Error from "@mui/icons-material/Error";
8
+ import Fade from "@mui/material/Fade";
9
+ import ResourceMenu from "../ResourceMenu";
10
+ import { SimpleTreeView } from "@mui/x-tree-view/SimpleTreeView";
11
+ import { TreeItem } from "@mui/x-tree-view/TreeItem";
12
+ import styles from "./styles";
13
+ import { publish, useEvent } from "@nucleoidai/react-event";
14
+ import { useTheme } from "@mui/material/styles";
15
+
16
+ import {
17
+ Box,
18
+ Card,
19
+ CardActions,
20
+ Fab,
21
+ Grid,
22
+ Menu,
23
+ MenuItem,
24
+ Tooltip,
25
+ Typography,
26
+ } from "@mui/material";
27
+ import React, { useEffect, useRef, useState } from "react";
28
+
29
+ function APITree() {
30
+ const map = useRef({});
31
+ const [selected, setSelected] = React.useState(null);
32
+ const [contextMenu, setContextMenu] = React.useState(null);
33
+ const [rightClickMethod, setRightClickMethod] = React.useState();
34
+ const [methodDisabled, setMethodDisabled] = useState();
35
+ const [open, setOpen] = useState(false);
36
+ const [resourceMenu, setResourceMenu] = React.useState(false);
37
+ const [anchor, setAnchor] = React.useState();
38
+ const [expanded, setExpanded] = useState([]);
39
+ const [errors] = useEvent("DIAGNOSTICS_COMPLETED", []);
40
+ const theme = useTheme();
41
+
42
+ const [selectedAPI] = useEvent("SELECTED_API_CHANGED", {
43
+ path: "/",
44
+ method: "GET",
45
+ });
46
+
47
+ const [api] = useEvent("API_DATA_CHANGED", []);
48
+ const apiExists = api.length > 0;
49
+
50
+ const expandList = React.useMemo(() => [], []);
51
+
52
+ const handleToggle = (event, ids) => {
53
+ setExpanded(ids);
54
+ };
55
+
56
+ const handleExpandClick = () => {
57
+ setExpanded([...expandList]);
58
+ };
59
+
60
+ const select = (id, callDispatch = true) => {
61
+ if (map.current[id]) {
62
+ setSelected(id);
63
+ if (callDispatch) {
64
+ publish("SELECTED_API_CHANGED", map.current[id]);
65
+ }
66
+ }
67
+ };
68
+
69
+ useEffect(() => {
70
+ const selectedId = Object.keys(map.current).find(
71
+ (key) =>
72
+ map.current[key].path === selectedAPI.path &&
73
+ map.current[key].method === selectedAPI.method
74
+ );
75
+ if (selectedId) {
76
+ select(selectedId, false);
77
+ }
78
+ // eslint-disable-next-line react-hooks/exhaustive-deps
79
+ }, [selectedAPI]);
80
+
81
+ const handleContextMenu = (event, hash) => {
82
+ event.preventDefault();
83
+
84
+ if (hash) select(hash);
85
+
86
+ setRightClickMethod(hash);
87
+
88
+ setContextMenu(
89
+ !contextMenu
90
+ ? {
91
+ mouseX: event.clientX,
92
+ mouseY: event.clientY,
93
+ }
94
+ : null
95
+ );
96
+ };
97
+
98
+ const handleResourceMenu = (event) => {
99
+ event.preventDefault();
100
+
101
+ setResourceMenu(true);
102
+ setAnchor(event);
103
+ };
104
+
105
+ const handleCloseResourceMenu = () => {
106
+ setResourceMenu(false);
107
+ };
108
+
109
+ const handleClose = () => {
110
+ setRightClickMethod(null);
111
+ setContextMenu(null);
112
+ };
113
+
114
+ const editMethod = () => {
115
+ publish("API_DIALOG_OPEN", { type: "method", action: "edit" });
116
+ handleClose();
117
+ };
118
+
119
+ const deleteMethod = () => {
120
+ publish("API_METHOD_DELETE", {});
121
+ setOpen(false);
122
+ };
123
+
124
+ const handleDeleteMethod = () => {
125
+ setOpen(true);
126
+ handleClose();
127
+ };
128
+
129
+ const checkMethodDeletable = () => {
130
+ const countMethodsForPath = (path) => {
131
+ return api.filter((endpoint) => endpoint.path === path).length;
132
+ };
133
+ if (selectedAPI) {
134
+ const apiSelectedPath = selectedAPI.path;
135
+
136
+ return countMethodsForPath(apiSelectedPath) <= 1;
137
+ }
138
+ return false;
139
+ };
140
+
141
+ useEffect(() => {
142
+ if (!selected) {
143
+ select(Object.keys(map.current)[0]);
144
+ }
145
+ setMethodDisabled(checkMethodDeletable());
146
+ handleExpandClick();
147
+
148
+ // eslint-disable-next-line react-hooks/exhaustive-deps
149
+ }, [selected, api, selectedAPI]);
150
+
151
+ return (
152
+ <Card sx={{ height: "100%" }}>
153
+ {apiExists ? (
154
+ <>
155
+ <Grid sx={styles.apiTreeGrid}>
156
+ {open && (
157
+ <DeleteMethodDialog
158
+ setOpen={setOpen}
159
+ deleteMethod={deleteMethod}
160
+ />
161
+ )}
162
+ <SimpleTreeView
163
+ slots={{
164
+ collapseIcon: () => <ArrowIcon down />,
165
+ expandIcon: () => <ArrowIcon right />,
166
+ }}
167
+ expandedItems={expanded}
168
+ onExpandedItemsChange={(event, itemIds) => setExpanded(itemIds)}
169
+ selectedItems={selected}
170
+ onSelectedItemsChange={(event, itemId) => select(itemId)}
171
+ sx={{
172
+ marginTop: "10px",
173
+ }}
174
+ >
175
+ {compile(
176
+ api,
177
+ handleContextMenu,
178
+ expandList,
179
+ rightClickMethod,
180
+ errors,
181
+ theme,
182
+ select,
183
+ handleResourceMenu,
184
+ map.current
185
+ )}
186
+ </SimpleTreeView>
187
+
188
+ <Menu
189
+ open={contextMenu !== null}
190
+ onClose={handleClose}
191
+ anchorReference="anchorPosition"
192
+ anchorPosition={
193
+ contextMenu !== null
194
+ ? { top: contextMenu.mouseY, left: contextMenu.mouseX }
195
+ : undefined
196
+ }
197
+ TransitionComponent={Fade}
198
+ >
199
+ <MenuItem
200
+ onClick={() => {
201
+ editMethod();
202
+ }}
203
+ data-cy="edit-method-menu-item"
204
+ >
205
+ <EditIcon />
206
+ <Typography sx={styles.menuItemText}>Edit</Typography>
207
+ </MenuItem>
208
+ <MenuItem
209
+ onClick={handleDeleteMethod}
210
+ disabled={methodDisabled}
211
+ data-cy="delete-method-button"
212
+ >
213
+ <DeleteIcon />
214
+ <Typography sx={styles.menuItemText}>Delete</Typography>
215
+ </MenuItem>
216
+ </Menu>
217
+ <ResourceMenu
218
+ anchor={anchor}
219
+ openMenu={resourceMenu}
220
+ handleClose={handleCloseResourceMenu}
221
+ />
222
+ </Grid>
223
+
224
+ <CardActions>
225
+ <Fab
226
+ variant="button"
227
+ size={"small"}
228
+ onClick={handleResourceMenu}
229
+ data-cy="resource-menu"
230
+ >
231
+ <AddIcon />
232
+ </Fab>
233
+ </CardActions>
234
+ </>
235
+ ) : (
236
+ <BlankTreeMessage item={"API"} />
237
+ )}
238
+ </Card>
239
+ );
240
+ }
241
+
242
+ export const compile = (
243
+ apiData,
244
+ handleContextMenu,
245
+ expandList,
246
+ rightClickMethod,
247
+ errors,
248
+ theme,
249
+ select,
250
+ handleResourceMenu,
251
+ map
252
+ ) => {
253
+ if (apiData.length !== 0) {
254
+ const groupedByPath = apiData.reduce((acc, endpoint) => {
255
+ const parts = endpoint.path.split("/");
256
+ let currentLevel = acc;
257
+
258
+ if (endpoint.path === "/") {
259
+ if (!currentLevel["/"]) {
260
+ currentLevel["/"] = {
261
+ methods: [],
262
+ children: {},
263
+ };
264
+ }
265
+ currentLevel["/"].methods.push(endpoint);
266
+ } else {
267
+ currentLevel = currentLevel["/"].children;
268
+
269
+ parts.forEach((part, idx, arr) => {
270
+ if (idx !== 0) {
271
+ const currentPart = "/" + part;
272
+
273
+ if (!currentLevel[currentPart]) {
274
+ currentLevel[currentPart] = {
275
+ methods: [],
276
+ children: {},
277
+ };
278
+ }
279
+
280
+ if (idx === arr.length - 1) {
281
+ currentLevel[currentPart].methods.push(endpoint);
282
+ } else {
283
+ currentLevel = currentLevel[currentPart].children;
284
+ }
285
+ }
286
+ });
287
+ }
288
+
289
+ return acc;
290
+ }, {});
291
+
292
+ const renderTree = (data) => {
293
+ // eslint-disable-next-line
294
+ let resourceHash;
295
+ // eslint-disable-next-line
296
+
297
+ return Object.keys(data).map((path) => {
298
+ const { methods, children } = data[path];
299
+
300
+ const methodItems = methods.map((method) => {
301
+ const payload = { path: method.path, method: method.method };
302
+ const hash = (resourceHash = window.btoa(JSON.stringify(payload)));
303
+ map[hash] = payload;
304
+
305
+ const error = errors.find((item) => {
306
+ const [errPath, errMethod] = item.file.fileName.split(".", 2);
307
+ if (errPath === method.path && errMethod === method.method) {
308
+ return item;
309
+ } else {
310
+ return null;
311
+ }
312
+ });
313
+
314
+ return (
315
+ <TreeItem
316
+ key={hash}
317
+ itemId={hash}
318
+ onContextMenu={(event) => handleContextMenu(event, hash)}
319
+ sx={{
320
+ bgcolor:
321
+ hash === rightClickMethod &&
322
+ (theme.palette.custom?.apiTreeRightClick ||
323
+ theme.palette.action.selected),
324
+ }}
325
+ label={
326
+ <Box
327
+ sx={{
328
+ display: "flex",
329
+ flexDirection: "row",
330
+ alignItems: "center",
331
+ justifyContent: "space-between",
332
+ }}
333
+ data-cy={`method-${method.path}${method.method}`}
334
+ >
335
+ <Box sx={theme.custom?.apiTreeItem}>
336
+ <span
337
+ style={{
338
+ display: "flex",
339
+ flexDirection: "row",
340
+ alignItems: "center",
341
+ justifyContent: "center",
342
+ }}
343
+ >
344
+ {method.method.toUpperCase()}
345
+ </span>
346
+ </Box>
347
+ {error && (
348
+ <Tooltip title={error.messageText} placement={"right"}>
349
+ <Error sx={{ color: "#8f8f91" }} />
350
+ </Tooltip>
351
+ )}
352
+ </Box>
353
+ }
354
+ />
355
+ );
356
+ });
357
+
358
+ const childItems = children ? renderTree(children) : [];
359
+ expandList.push(path);
360
+
361
+ const handleResourceClick = (event) => {
362
+ event.stopPropagation();
363
+
364
+ if (methods.length > 0) {
365
+ const firstMethod = methods[0];
366
+ const payload = {
367
+ path: firstMethod.path,
368
+ method: firstMethod.method,
369
+ };
370
+ const hash = window.btoa(JSON.stringify(payload));
371
+ select(hash);
372
+ }
373
+ };
374
+
375
+ const handleResourceContextMenu = (event) => {
376
+ event.preventDefault();
377
+ handleResourceClick(event);
378
+ handleResourceMenu(event);
379
+ };
380
+
381
+ return (
382
+ <TreeItem
383
+ key={path}
384
+ itemId={path}
385
+ label={
386
+ <div
387
+ className="path"
388
+ onClick={(e) => e.stopPropagation()}
389
+ onContextMenu={handleResourceContextMenu}
390
+ style={{ cursor: "default" }}
391
+ data-cy={`path-${path}`}
392
+ >
393
+ {path}
394
+ </div>
395
+ }
396
+ >
397
+ {[...methodItems, ...childItems]}
398
+ </TreeItem>
399
+ );
400
+ });
401
+ };
402
+
403
+ return renderTree(groupedByPath);
404
+ } else {
405
+ return null;
406
+ }
407
+ };
408
+
409
+ export default APITree;