@canmingir/link 1.2.22 → 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.
- package/package.json +3 -1
- package/src/Platform.jsx +10 -14
- package/src/config/schemas.js +1 -0
- package/src/context/Context.js +98 -0
- package/src/context/reducer.js +590 -10
- package/src/layouts/auth/modern.jsx +2 -0
- package/src/layouts/common/account-popover.jsx +3 -7
- package/src/lib/APIDialogAction/APIDialogAction.jsx +109 -0
- package/src/lib/APIDialogAction/index.js +1 -0
- package/src/lib/APIDialogAction/styles.js +6 -0
- package/src/lib/APIParams/APIParams.jsx +57 -0
- package/src/lib/APIParams/index.js +1 -0
- package/src/lib/APIPath/APIPath.jsx +82 -0
- package/src/lib/APIPath/index.js +1 -0
- package/src/lib/APIPath/styles.js +19 -0
- package/src/lib/APITree/APITree.jsx +409 -0
- package/src/lib/APITree/Arrow.jsx +21 -0
- package/src/lib/APITree/DeleteMethodDialog.jsx +41 -0
- package/src/lib/APITree/index.js +1 -0
- package/src/lib/APITree/styles.js +19 -0
- package/src/lib/APITypes/APITypes.jsx +141 -0
- package/src/lib/APITypes/TypeEditor.jsx +46 -0
- package/src/lib/APITypes/TypeList.jsx +180 -0
- package/src/lib/APITypes/index.js +1 -0
- package/src/lib/BlankTreeMessage/BlankTreeMessage.jsx +39 -0
- package/src/lib/BlankTreeMessage/index.js +1 -0
- package/src/lib/DialogTootip/DialogTooltip.jsx +67 -0
- package/src/lib/DialogTootip/index.js +1 -0
- package/src/lib/DialogTootip/styles.js +9 -0
- package/src/lib/NewApiBody/NewAPIBody.jsx +97 -0
- package/src/lib/NewApiBody/ParamView.jsx +38 -0
- package/src/lib/NucDialog/NucDialog.jsx +108 -0
- package/src/lib/NucDialog/index.js +1 -0
- package/src/lib/ParamTable/ParamTable.jsx +133 -0
- package/src/lib/ParamTable/TypeMenu.jsx +102 -0
- package/src/lib/ParamTable/defaults.js +47 -0
- package/src/lib/ParamTable/index.js +1 -0
- package/src/lib/ParamTable/styles.js +12 -0
- package/src/lib/ResourceMenu/AlertMassage.jsx +28 -0
- package/src/lib/ResourceMenu/DeleteResourceDialog.jsx +60 -0
- package/src/lib/ResourceMenu/ResourceMenu.jsx +156 -0
- package/src/lib/ResourceMenu/index.js +1 -0
- package/src/lib/ResourceMenu/styles.js +5 -0
- package/src/lib/Schema/Schema.jsx +204 -0
- package/src/lib/Schema/index.js +1 -0
- package/src/lib/SchemaEditor/SchemaEditor.jsx +258 -0
- package/src/lib/SchemaEditor/SchemaEditor.test.js +193 -0
- package/src/lib/SchemaEditor/SchemaPropertyEditor.jsx +135 -0
- package/src/lib/SchemaEditor/SchemaUtils.js +152 -0
- package/src/lib/SchemaEditor/index.js +1 -0
- package/src/lib/ToggleableMenu/ToggleableMenu.jsx +35 -0
- package/src/lib/ToggleableMenu/index.js +1 -0
- package/src/lib/index.js +14 -0
- package/src/pages/Callback.jsx +6 -8
- package/src/stories/APITree.stories.jsx +429 -0
- package/src/stories/FlowChart.stories.jsx +1 -1
- package/src/templates/ActionTemplate.js +24 -0
- package/src/widgets/Login/CognitoLogin.jsx +4 -2
- package/src/widgets/Login/DemoLogin.jsx +8 -6
- package/src/widgets/SettingsDialog.jsx +38 -16
|
@@ -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,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;
|