Skip to content

Commit

Permalink
Merge pull request #6 from amir78729/fix/object-modification
Browse files Browse the repository at this point in the history
Fix: update object functionality
  • Loading branch information
amir78729 authored Jun 20, 2024
2 parents f6bd3ff + 2b9ec5b commit b8cc53b
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 37 deletions.
16 changes: 10 additions & 6 deletions src/builder/JsonSchemaBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {JsonSchema, JsonSchemaType, Format} from "../types";
import {deleteNestedPropertyByPath, generatePath, updateNestedObjectByPath} from "../utils";


/**
Expand Down Expand Up @@ -28,6 +29,13 @@ export class JsonSchemaBuilder {
this.schema.properties[name] = propSchema;
return this;
}
addNestedProperty(name: string, propSchema: JsonSchema): JsonSchemaBuilder {
if (!this.schema.properties) {
this.schema.properties = {};
}
this.schema = updateNestedObjectByPath(this.schema, name, propSchema);
return this;
}

addRequired(...args: string[]): JsonSchemaBuilder {
if (!this.schema.required) {
Expand Down Expand Up @@ -158,9 +166,7 @@ export class JsonSchemaBuilder {
}

deleteProperty(name: string): JsonSchemaBuilder {
if (this.schema.properties && this.schema.properties[name]) {
delete this.schema.properties[name];
}
this.schema = deleteNestedPropertyByPath(this.schema, name)
return this;
}

Expand All @@ -172,9 +178,7 @@ export class JsonSchemaBuilder {
}

editProperty(name: string, propSchema: JsonSchema): JsonSchemaBuilder {
if (this.schema.properties && this.schema.properties[name]) {
this.schema.properties[name] = propSchema;
}
this.schema = updateNestedObjectByPath(this.schema, name, propSchema)
return this;
}

Expand Down
7 changes: 4 additions & 3 deletions src/components/AddFieldModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import {JsonSchemaType} from "../types";
import {JsonSchemaField} from "../fields/JsonSchemaField";
import Select from "@mui/material/Select";
import {Add} from "@mui/icons-material";
import {generatePath} from "../utils";

const AddFieldModal = () => {
const AddFieldModal = ({ parentPath }) => {
const [open, setOpen] = React.useState<boolean>(false);
const [name, setName] = React.useState<string | null>(null);
const [type, setType] = React.useState<JsonSchemaType | null>(null);
Expand All @@ -33,15 +34,15 @@ const AddFieldModal = () => {
dispatch({
type: "ADD_PROPERTY",
payload: {
name: field.getName() || 'newField',
name: generatePath(parentPath, field.getName() || 'newField'),
schema: field.getSchema(),
},
});
if (field.getIsRequired()) {
dispatch({
type: "ADD_REQUIRED",
payload: {
name: field.getName() || 'newField',
name: generatePath(parentPath, field.getName() || 'newField'),
},
})
}
Expand Down
55 changes: 30 additions & 25 deletions src/components/SchemaPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
Tooltip,
Typography
} from "@mui/material";
import {getFieldId, getSchemaFormatFromSchema} from "../utils";
import {generatePath, getFieldId, getSchemaFormatFromSchema} from "../utils";
import {DataVisualizationType} from "../types";
import {SchemaAction, useSchema} from "../providers/SchemaProvider";
import Form from "@rjsf/mui";
Expand All @@ -35,11 +35,11 @@ type Props = {
schema: RJSFSchema;
data: unknown;
name: string;
path?: string;
}


// TODO: refactor
const renderHeader = ({icon, schema, onDelete, name}: {
const renderHeader = ({icon, schema, onDelete, name, path}: {
icon?: React.ReactNode,
schema: RJSFSchema,
name: string,
Expand All @@ -63,23 +63,24 @@ const renderHeader = ({icon, schema, onDelete, name}: {
<DialogTitle>Edit <code>{name}</code> Field</DialogTitle>
<DialogContent>
<Form onSubmit={({formData}) => {
dispatch({type: "UPDATE_PROPERTY", payload: {name, schema: formData}})
handleEdit(dispatch, path, formData)
setShowEditModal(false);
}} schema={field?.getBuilderSchema()} formData={schema} validator={validator}/>
</DialogContent>
</Dialog>

<Dialog open={showPreviewModal} onClose={() => setShowPreviewModal(false)}>
<DialogTitle><code>{name}</code> Field <span>{onDelete && <IconButton color="error" onClick={() => setShowDeleteConfirmationModal(true)}><Delete
fontSize="small"/></IconButton>}
<DialogTitle><code>{name}</code> Field <span>{onDelete &&
<IconButton color="error" onClick={() => setShowDeleteConfirmationModal(true)}><Delete
fontSize="small"/></IconButton>}
<IconButton
color="warning"
onClick={() => setShowEditModal(true)}
>
<Edit fontSize="small"/>
</IconButton></span></DialogTitle>
<DialogContent>
<FieldPreview name={name} schema={field?.getBuilderSchema()} data={schema} />
<FieldPreview name={name} schema={field?.getBuilderSchema()} data={schema}/>
</DialogContent>
</Dialog>

Expand Down Expand Up @@ -142,43 +143,43 @@ const handleEdit = (dispatch: React.Dispatch<SchemaAction>, name: string, schema
dispatch({type: "UPDATE_PROPERTY", payload: {name, schema}});
}

const SchemaPreview = ({schema, data, name}: Props) => {
const SchemaPreview = ({schema, data, name, path}: Props) => {
const FormPreview = getSchemaFormatFromSchema(schema, SchemaPreview)
return (
<div>
<FormPreview {...{schema, data, name}} />
<FormPreview {...{schema, data, name, path}} />
</div>
)
};

SchemaPreview.String = function String({schema, data, name}: DataVisualizationType) {
SchemaPreview.String = function String({schema, path, data, name}: DataVisualizationType) {
const {dispatch} = useSchema();
return (
<div>
{renderHeader({name, schema, icon: <TextSnippet/>, onDelete: () => handleDelete(dispatch, name)})}
{renderHeader({name, path, schema, icon: <TextSnippet/>, onDelete: () => handleDelete(dispatch, path)})}
</div>
);
};

SchemaPreview.Number = function Number({schema, name}: DataVisualizationType) {
SchemaPreview.Number = function Number({schema, path, name}: DataVisualizationType) {
const {dispatch} = useSchema();
return (
<div>
{renderHeader({name, schema, icon: <Numbers/>, onDelete: () => handleDelete(dispatch, name)})}
{renderHeader({name, path, schema, icon: <Numbers/>, onDelete: () => handleDelete(dispatch, path)})}
</div>
);
};

SchemaPreview.Boolean = function BooleanVisualization({schema, name}: DataVisualizationType) {
SchemaPreview.Boolean = function BooleanVisualization({schema, path, name}: DataVisualizationType) {
const {dispatch} = useSchema();
return (
<div>
{renderHeader({name, schema, icon: <ToggleOn/>, onDelete: () => handleDelete(dispatch, name)})}
{renderHeader({name, path, schema, icon: <ToggleOn/>, onDelete: () => handleDelete(dispatch, path)})}
</div>
);
};

SchemaPreview.Object = function ObjectVisualization({schema, data, name}: DataVisualizationType) {
SchemaPreview.Object = function ObjectVisualization({schema, path, data, name}: DataVisualizationType) {
const {dispatch} = useSchema();
const properties = Object.keys(schema?.properties || {})

Expand All @@ -192,27 +193,31 @@ SchemaPreview.Object = function ObjectVisualization({schema, data, name}: DataVi
<Card sx={{p: 2, m: 2}}>
{renderHeader({
name,
path,
schema,
icon: <DataObject/>,
collapse: open,
onCollapse: handleCollapse,
onDelete: () => handleDelete(dispatch, name)
onDelete: () => handleDelete(dispatch, path)
})}
<Card sx={{p: 2, m: 2}}>
<Box
px={2} display="flex" justifyContent="space-between">
<Typography flex={1}>Properties</Typography>
<AddFieldModal/>
<AddFieldModal parentPath={generatePath(path, 'properties')}/>
{open !== undefined &&
<IconButton onClick={handleCollapse}>{!open ? <ExpandMore fontSize="small"/> :
<ExpandLess fontSize="small"/>}</IconButton>}
</Box>
<Collapse in={open} timeout="auto" unmountOnExit>
{properties?.length > 0 ? properties?.map((property) => (
<SchemaPreview
name={property}
schema={schema.properties[property]}
/>
<>
<SchemaPreview
name={property}
schema={schema.properties[property]}
path={generatePath(path, generatePath('properties', property))}
/>
</>
)) : (
<Typography alignItems="center" textAlign="center" p={3}>
Click on <Add fontSize="small"/> button to add properties
Expand All @@ -224,14 +229,14 @@ SchemaPreview.Object = function ObjectVisualization({schema, data, name}: DataVi
);
};

SchemaPreview.Array = function ArrayVisualization({schema, data, name}: DataVisualizationType) {
SchemaPreview.Array = function ArrayVisualization({schema, path, data, name}: DataVisualizationType) {
const {dispatch} = useSchema();
return (
<>
<Card sx={{p: 2, m: 2}}>
{renderHeader({name, schema, icon: <DataArray/>, onDelete: () => handleDelete(dispatch, name)})}
{renderHeader({name,path, schema, icon: <DataArray/>, onDelete: () => handleDelete(dispatch, path)})}
<SchemaPreview
{...{schema: schema.items, name}}
{...{schema: schema.items, name, path: generatePath(path, 'items')}}
/>
</Card>
</>
Expand Down
2 changes: 1 addition & 1 deletion src/providers/SchemaProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ const schemaReducer = (state: JsonSchema, action: SchemaAction): JsonSchema => {

switch (action.type) {
case "ADD_PROPERTY":
builder.addProperty(action.payload.name, action.payload.schema!);
builder.addNestedProperty(action.payload.name, action.payload.schema!);
break;
case "UPDATE_PROPERTY":
builder.editProperty(action.payload.name, action.payload.schema!);
Expand Down
3 changes: 3 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,7 @@ export type DataVisualizationType = {
data?: object;
schema: RJSFSchema;
name?: string
path?: string
}

export type NestedObject = { [key: string]: any };
62 changes: 60 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {RJSFSchema} from "@rjsf/utils";
import {DataVisualizationType} from "./types";
import {DataVisualizationType, NestedObject} from "./types";

export const getSchemaFormatFromSchema = (
schema: RJSFSchema,
Expand Down Expand Up @@ -48,4 +48,62 @@ export const getFieldId = (schema: RJSFSchema) => {
if (schema?.type === 'number' || schema?.type === 'integer') return 'NUMBER';
if (schema?.type === 'object') return 'OBJECT';
if (schema?.type === 'array') return 'ARRAY';
}
}

export const generatePath = (parentPath: string = '', fieldName: string): string => {
let path = parentPath;
if (path?.length > 0) path += '.';
path += fieldName;
return path;
}

export const accessToObjectFieldByPath = (object: object, path: string) => {
return path.split('.').reduce((o, i) => o[i], object)
}

export const updateNestedObjectByPath = (obj: NestedObject, path: string, value: any): NestedObject => {
const keys = path.split('.');
const newObject = {...obj};

let current = newObject;
keys.forEach((key, index) => {
if (index === keys.length - 1) {
current[key] = value;
} else {
current[key] = current[key] ? {...current[key]} : {};
current = current[key];
}
});

return newObject;
}

export const deleteNestedPropertyByPath = (obj: NestedObject, path: string): NestedObject => {
const keys = path.split('.');
const newObject = {...obj};

if (keys.length === 0) {
return newObject;
}

let current = newObject;
const stack = [];

for (let i = 0; i < keys.length - 1; i++) {
stack.push(current);
current[keys[i]] = {...current[keys[i]]};
current = current[keys[i]];
}

delete current[keys[keys.length - 1]];

for (let i = keys.length - 2; i >= 0; i--) {
const key = keys[i];
current = stack.pop();
if (Object.keys(current[key]).length === 0) {
delete current[key];
}
}

return newObject;
}

0 comments on commit b8cc53b

Please sign in to comment.