Skip to content

Commit

Permalink
fix: Enhancement of Visual Json Schema Editor (#1065)
Browse files Browse the repository at this point in the history
Co-authored-by: Khuda Dad Nomani <[email protected]>%0ACo-authored-by: Prince Rajpoot <[email protected]>
  • Loading branch information
Gmin2 and princerajpoot20 authored Jun 6, 2024
1 parent 84f76fa commit 2a3b710
Show file tree
Hide file tree
Showing 10 changed files with 14,048 additions and 11,049 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -282,4 +282,39 @@ export const NesteadArray = () => (
/>
);

export const Blank_schema = () => (
<Template
initialSchema={JSON.stringify(
{}, null, 2)}
/>
);

export const Root_string = () => (
<Template
initialSchema={JSON.stringify(
{
"type": "string"
}, null, 2)}
/>
);

export const Multiple_data_types = () => (
<Template
initialSchema={JSON.stringify(
{
type: "object",
properties: {
mixedTypeProperty: {
type: ["boolean", "integer"],
},
},
required: ["mixedTypeProperty"],
},
null,
2
)}
/>
);


export default VisualEditorStory
2 changes: 2 additions & 0 deletions packages/ui/components/DropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface DropdownMenuProps {
items: DropdownMenuItem[]
side?: 'top' | 'right' | 'bottom' | 'left'
align?: 'start' | 'center' | 'end'
onSelect?: (options: string[]) => void
}

interface DropdownMenuItemComponentProps {
Expand All @@ -44,6 +45,7 @@ export const DropdownMenu: FunctionComponent<DropdownMenuProps> = ({
items,
side,
align,
onSelect
}) => {
return (
<RadixDropdownMenu.Root>
Expand Down
189 changes: 139 additions & 50 deletions packages/ui/components/VisualEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import React, { useState, useEffect } from 'react';
import SchemaObject from './VisualEditor/SchemaObject';
import _ from 'lodash';
import { getColorForType } from './VisualEditor/SchemaProperty';
import { DropdownMenu, DropdownMenuItem } from './DropdownMenu';
import { IoIosArrowDropdown } from 'react-icons/io';

interface VisualEditorProps {
schema: string;
Expand All @@ -15,14 +18,7 @@ interface SchemaObjectInterface {
}

export const VisualEditor: React.FC<VisualEditorProps> = ({ schema, onSchemaChange }) => {
const selectStyle = {
backgroundColor: '#0F172A',
color: 'white',
borderRadius: '3px',
fontSize: '12px',
fontFamily: 'Inter, sans-serif'
};


const [schemaObject, setSchemaObject] = useState<SchemaObjectInterface>({});

useEffect(() => {
Expand All @@ -36,64 +32,157 @@ export const VisualEditor: React.FC<VisualEditorProps> = ({ schema, onSchemaChan
}, [schema]);

const handleSchemaChange = (updatedPart: any) => {
const updatedSchema = { ...schemaObject, ...updatedPart };
let updatedSchema = _.cloneDeep(schemaObject);
console.log("updatedPart.type", updatedPart.type)

if (updatedSchema.type === 'array') {
if (updatedPart.items && updatedPart.items.type === 'object') {
updatedSchema.items = updatedPart.items;
} else if (updatedPart?.items?.properties) {
updatedSchema.items = {
...updatedSchema.items,
properties: {
...updatedSchema.items.properties,
...updatedPart.items.properties,
},
};
} else if (updatedPart.type !== 'object' ) {
updatedSchema = { ...schemaObject, ...updatedPart };
} else if (Object.keys(updatedPart.properties).length === 0 && updatedPart?.required === undefined){
updatedSchema = { ...schemaObject, ...updatedPart };
} else {
updatedSchema.items = { ...updatedSchema.items, ...updatedPart };
}
} else {
updatedSchema = { ...schemaObject, ...updatedPart };
}

const newSchemaString = JSON.stringify(updatedSchema);
console.log('Schema updated:', newSchemaString);
setSchemaObject(updatedSchema);
onSchemaChange(newSchemaString);
};

const renderRootTypeSelector = () => (
<div>
<select
value={schemaObject.type || ''}
onChange={(e) => handleSchemaChange({ type: e.target.value })}
style={selectStyle}
>
<option value="">Select root type</option>
<option value="object">Object</option>
<option value="array">Array</option>
<option value="string">String</option>
<option value="number">Number</option>
<option value="boolean">Boolean</option>
</select>
</div>
);
const handleRootTypeDropdownSelect = (selectedOption: string) => {
if (selectedOption === 'array') {
handleSchemaChange({ type: 'array', items: { type: 'string' }, properties: undefined, required: undefined});
} else {
handleSchemaChange({ type: selectedOption, properties: undefined, required: undefined});
}
};

const handleArrayItemTypeDropdownSelect = (selectedOption: string) => {
handleSchemaChange({ items: schemaObject.items ? { ...schemaObject.items, type: selectedOption } : { type: selectedOption } });
};

const rootTypeOptions: DropdownMenuItem[] = [
{ title: 'Select Root Type',onSelect: () => {}},
{ type: 'separator'},
{ type: 'regular', title: 'Object', onSelect: () => handleRootTypeDropdownSelect('object') },
{ type: 'regular', title: 'Array', onSelect: () => handleRootTypeDropdownSelect('array') },
{ type: 'regular', title: 'String', onSelect: () => handleRootTypeDropdownSelect('string') },
{ type: 'regular', title: 'Number', onSelect: () => handleRootTypeDropdownSelect('number') },
{ type: 'regular', title: 'Boolean', onSelect: () => handleRootTypeDropdownSelect('boolean') },
];

const itemTypeOptions: DropdownMenuItem[] = [
{ title: 'Select Items Type',onSelect: () => {}},
{ type: 'separator'},
{ type: 'regular', title: 'String', onSelect: () => handleArrayItemTypeDropdownSelect('string') },
{ type: 'regular', title: 'Number', onSelect: () => handleArrayItemTypeDropdownSelect('number') },
{ type: 'regular', title: 'Boolean', onSelect: () => handleArrayItemTypeDropdownSelect('boolean') },
{ type: 'regular', title: 'Object', onSelect: () => handleArrayItemTypeDropdownSelect('object') },
];

const renderArrayItemTypeSelector = () => {
if (schemaObject.type === 'array') {
const renderRootTypeDisplay = () => {
if(schemaObject.type === 'array') {
return null;
}
const rootType = schemaObject.type || '';
const displayRootType = rootType.charAt(0).toUpperCase() + rootType.slice(1);
return (
<div className="flex items-center">
<span
style={{
color: getColorForType(rootType),
borderRadius: '3px',
padding: '2px 4px',
fontSize: '14px',
fontFamily: 'Inter, Helvetica',
}}
>
{displayRootType}
</span>
</div>
);
};

const renderArrayItemTypeDisplay = () => {
if (schemaObject.type === 'array' && schemaObject.items) {
const itemType = schemaObject.items?.type || '';
return (
<div>
<strong>Array Item Type:</strong>
<select
value={schemaObject.items?.type || ''}
onChange={(e) => handleSchemaChange({ items: { ...schemaObject.items, type: e.target.value } })}
style={selectStyle}
<div className="flex items-center">
<span
style={{
color: getColorForType('array', itemType),
borderRadius: '3px',
padding: '2px 4px',
fontSize: '14px',
fontFamily: 'Inter, Helvetica',
}}
>
<option value="">Select item type</option>
<option value="string">String</option>
<option value="number">Number</option>
<option value="boolean">Boolean</option>
<option value="object">Object</option>
<option value="array">Array</option>
</select>
{`Array<${itemType}>`}
</span>
</div>
);
}
return null;
};

return (
<div className="visual-editor" style={{ width: '45vw', minWidth: '550px', background: '#0F172A', color: '#CBD5E1', fontFamily: 'Inter, sans-serif', padding: '20px'}}>
{renderRootTypeSelector()}
{renderArrayItemTypeSelector()}

<SchemaObject
schema={schemaObject.type === 'array' ? schemaObject.items : schemaObject}
onSchemaChange={(newSchema: any) => handleSchemaChange(newSchema)}
path={schemaObject.type === 'array' ? 'items' : ''}
level={0}
/>
<div
className="visual-editor"
style={{
width: "45vw",
minWidth: "550px",
background: "#0F172A",
color: "#CBD5E1",
fontFamily: "Inter, sans-serif",
padding: "20px",
}}
>
<div className="flex items-center gap-2">
{renderRootTypeDisplay()}
{renderArrayItemTypeDisplay()}
<DropdownMenu
trigger={
<button>
<IoIosArrowDropdown />
</button>
}
items={rootTypeOptions}
/>
{schemaObject.type === "array" && (
<DropdownMenu
trigger={
<button>
<IoIosArrowDropdown />
</button>
}
items={itemTypeOptions}
/>
)}
</div>
{(schemaObject.type === "object" || (schemaObject.type === "array" && schemaObject.items.type === "object")) && (
<SchemaObject
schema={
schemaObject.type === "array" ? schemaObject.items : schemaObject
}
onSchemaChange={(newSchema: any) => handleSchemaChange(newSchema)}
path={schemaObject.type === "array" ? "items" : ""}
level={0}
/>
)}
</div>
);
};
Expand Down
3 changes: 1 addition & 2 deletions packages/ui/components/VisualEditor/CodeEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ interface CodeEditorProps {
}

export const CodeEditor: React.FC<CodeEditorProps> = ({ schema, onSchemaChange }) => {
const [value, setValue] = useState<string>(schema);
const [value, setValue] = useState(schema);
const [error, setError] = useState<string>('');

useEffect(() => {
Expand Down Expand Up @@ -37,7 +37,6 @@ export const CodeEditor: React.FC<CodeEditorProps> = ({ schema, onSchemaChange }

return (
<div className="code-editor">
<h2>Code Editor</h2>
{error && <p style={{ color: 'red' }}>Error: {error}</p>}
<textarea
value={value}
Expand Down
Loading

0 comments on commit 2a3b710

Please sign in to comment.