Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SchemaEditor UX Improve #123

Merged
merged 13 commits into from
Jan 10, 2024
202 changes: 202 additions & 0 deletions src/components/Schema/Schema.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import { v4 as uuidv4 } from "uuid";

import { Box, Typography } from "@mui/material";
import React, { useEffect, useState } from "react";
import { TreeItem, TreeView } from "@mui/lab";

const Schema = ({ initialData = {}, customTypes = [] }) => {
const [schemaData, setSchemaData] = useState({});

useEffect(() => {
const addIdsToSchema = (schema) => {
return {
...schema,
id: uuidv4(),
properties: schema.properties?.map(addIdsToSchema),
};
};

if (!schemaData || Object.keys(schemaData).length === 0) {
if (Object.keys(initialData).length === 0) {
setSchemaData({
type: "object",
properties: [],
});
} else {
const dataWithIds = addIdsToSchema(initialData);
setSchemaData(dataWithIds);
}
}
}, [initialData, schemaData]);

const renderTree = (node, level = 0) => (
<TreeItem
key={node.id}
nodeId={level === 0 ? "1" : node.id}
label={
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
width: "100%",
}}
>
<div
style={{
display: "flex",
alignItems: "center",
gap: "1px",
width: "100%",
}}
>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
width: "100%",
gap: "4px",
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
cursor: "pointer",
}}
>
<Typography
variant="body2"
sx={{
padding: "2px 2px",
borderRadius: "4px",
}}
>
{node.name}
</Typography>
</Box>

<Box
sx={{
display: "flex",
alignItems: "center",
cursor: "pointer",
}}
>
<Typography
variant="body2"
sx={{
cursor: "pointer",
borderRadius: "4px",
}}
>
{node.type}
</Typography>
</Box>
</Box>
</div>
</div>
}
>
{Array.isArray(node.properties)
? node.properties.map((childNode) => renderTree(childNode, level + 1))
: isCustomType(node.type)
? renderCustomTypeNode(node)
: null}
</TreeItem>
);

const isCustomType = (type) => {
return customTypes.some((customType) => customType.name === type);
};

const renderCustomTypeNode = (node) => {
const customTypeSchema = customTypes.find(
(type) => type.name === node.type
)?.schema;

if (!customTypeSchema || !customTypeSchema.properties) {
return <Box sx={{ paddingLeft: "20px" }}>No properties defined</Box>;
}

return (
<Box sx={{ paddingLeft: "20px" }}>
{customTypeSchema.properties.map((prop, index) => (
<Box
key={index}
sx={{
paddingTop: "5px",
paddingBottom: "5px",
display: "flex",
alignItems: "center",
}}
>
<Typography
variant="body2"
sx={{
color: (theme) => theme.palette.grey[600],
}}
>
{prop.name}
</Typography>
<Typography
variant="body2"
sx={{
marginLeft: "8px",
color: (theme) => theme.palette.grey[500],
}}
>
{prop.type}
</Typography>
</Box>
))}
</Box>
);
};

return (
<TreeView
defaultCollapseIcon={<ExpandMoreIcon />}
defaultExpandIcon={<ChevronRightIcon />}
defaultExpanded={["1"]}
sx={{
flexGrow: 1,
overflowY: "auto",
width: "100%",
".MuiTreeItem-root": {
alignItems: "center",
},
".MuiTreeItem-content": {
width: "100%",
display: "flex",
justifyContent: "space-between",
padding: "1px 8px",
borderRadius: "4px",
margin: "1px 0",
transition: "all 0.3s",
},
".MuiTreeItem-label": {
width: "100%",
fontWeight: "bold",
},
".MuiTreeItem-group": {
marginLeft: "16px !important",
paddingLeft: "8px",
borderLeft: `1px solid`,
borderColor: (theme) => theme.palette.grey[400],
},
".MuiTreeItem-iconContainer": {
minWidth: "0",
marginRight: "0px",
padding: "0px",
},
}}
>
{renderTree(schemaData, 0)}
</TreeView>
);
};

export default Schema;
88 changes: 66 additions & 22 deletions src/components/SchemaEditor/SchemaEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,10 @@ import RemoveCircleOutlineIcon from "@mui/icons-material/RemoveCircleOutline";
import SchemaPropertyEditor from "./SchemaPropertyEditor";
import { v4 as uuidv4 } from "uuid";

import { Box, Typography } from "@mui/material";
import React, { forwardRef, useEffect, useState } from "react";
import { TreeItem, TreeView } from "@mui/lab";
import {
addProperty,
changeProperty,
getTypeStyle,
removeProperty,
} from "./SchemaUtils";
import { addProperty, changeProperty, removeProperty } from "./SchemaUtils";

const SchemaEditor = forwardRef(
({ initialData = {}, customTypes = [] }, ref) => {
Expand Down Expand Up @@ -42,7 +38,7 @@ const SchemaEditor = forwardRef(
}, [initialData, schemaData]);

const handleAddProperty = (newProperty, parentId = null) => {
addProperty(newProperty, parentId, setSchemaData);
addProperty(parentId, setSchemaData);
};

const handleRemoveProperty = (propertyId) => {
Expand All @@ -66,7 +62,14 @@ const SchemaEditor = forwardRef(
width: "100%",
}}
>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<div
style={{
display: "flex",
alignItems: "center",
gap: "1px",
width: "100%",
}}
>
<SchemaPropertyEditor
node={node}
onNameChange={(newName) => {
Expand All @@ -83,7 +86,7 @@ const SchemaEditor = forwardRef(
}}
customTypes={customTypes}
/>
{(node.type === "object" || node.type === "array") && (
{true && (
<IconButton
size="small"
onClick={(e) => {
Expand All @@ -95,17 +98,20 @@ const SchemaEditor = forwardRef(
);
}}
disabled={
node.type === "array" &&
node.properties &&
node.properties.length >= 1
node.type !== "object" ||
(node.type === "array" && node.properties.length >= 1)
}
sx={{
color: (theme) => theme.palette.grey[600],
marginRight: "-8px ",
}}
>
<AddCircleOutlineIcon fontSize="small" />
</IconButton>
)}
</div>

{level > 0 && (
{true && (
<IconButton
size="small"
style={{ marginLeft: "auto" }}
Expand All @@ -114,6 +120,10 @@ const SchemaEditor = forwardRef(
e.stopPropagation();
handleRemoveProperty(node.id);
}}
disabled={level === 0}
sx={{
color: (theme) => theme.palette.grey[600],
}}
>
<RemoveCircleOutlineIcon fontSize="small" />
</IconButton>
Expand All @@ -139,23 +149,41 @@ const SchemaEditor = forwardRef(
)?.schema;

if (!customTypeSchema || !customTypeSchema.properties) {
return <div style={{ paddingLeft: "20px" }}>No properties defined</div>;
return <Box sx={{ paddingLeft: "20px" }}>No properties defined</Box>;
}

return (
<div style={{ paddingLeft: "20px" }}>
<Box sx={{ paddingLeft: "20px" }}>
{customTypeSchema.properties.map((prop, index) => (
<div
<Box
key={index}
style={{ paddingTop: "5px", paddingBottom: "5px" }}
sx={{
paddingTop: "5px",
paddingBottom: "5px",
display: "flex",
alignItems: "center",
}}
>
<span>{prop.name}:</span>
<span style={{ ...getTypeStyle(prop.type), marginLeft: "8px" }}>
<Typography
variant="body2"
sx={{
color: (theme) => theme.palette.grey[600],
}}
>
{prop.name}
</Typography>
<Typography
variant="body2"
sx={{
marginLeft: "8px",
color: (theme) => theme.palette.grey[500],
}}
>
{prop.type}
</span>
</div>
</Typography>
</Box>
))}
</div>
</Box>
);
};

Expand Down Expand Up @@ -199,9 +227,25 @@ const SchemaEditor = forwardRef(
width: "100%",
display: "flex",
justifyContent: "space-between",
padding: "1px 8px",
borderRadius: "4px",
margin: "1px 0",
transition: "all 0.3s",
},
".MuiTreeItem-label": {
width: "100%",
fontWeight: "bold",
},
".MuiTreeItem-group": {
marginLeft: "16px !important",
paddingLeft: "8px",
borderLeft: `1px solid`,
borderColor: (theme) => theme.palette.grey[400],
},
".MuiTreeItem-iconContainer": {
minWidth: "0",
marginRight: "0px",
padding: "0px",
},
}}
>
Expand Down
Loading
Loading