Skip to content

Commit 2a3b710

Browse files
fix: Enhancement of Visual Json Schema Editor (#1065)
Co-authored-by: Khuda Dad Nomani <32505158+KhudaDad414@users.noreply.github.com>%0ACo-authored-by: Prince Rajpoot <44585452+princerajpoot20@users.noreply.github.com>
1 parent 84f76fa commit 2a3b710

File tree

10 files changed

+14048
-11049
lines changed

10 files changed

+14048
-11049
lines changed

apps/design-system/src/components/VisualJsonSchemaEditor.stories.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,4 +282,39 @@ export const NesteadArray = () => (
282282
/>
283283
);
284284

285+
export const Blank_schema = () => (
286+
<Template
287+
initialSchema={JSON.stringify(
288+
{}, null, 2)}
289+
/>
290+
);
291+
292+
export const Root_string = () => (
293+
<Template
294+
initialSchema={JSON.stringify(
295+
{
296+
"type": "string"
297+
}, null, 2)}
298+
/>
299+
);
300+
301+
export const Multiple_data_types = () => (
302+
<Template
303+
initialSchema={JSON.stringify(
304+
{
305+
type: "object",
306+
properties: {
307+
mixedTypeProperty: {
308+
type: ["boolean", "integer"],
309+
},
310+
},
311+
required: ["mixedTypeProperty"],
312+
},
313+
null,
314+
2
315+
)}
316+
/>
317+
);
318+
319+
285320
export default VisualEditorStory

packages/ui/components/DropdownMenu.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ interface DropdownMenuProps {
1818
items: DropdownMenuItem[]
1919
side?: 'top' | 'right' | 'bottom' | 'left'
2020
align?: 'start' | 'center' | 'end'
21+
onSelect?: (options: string[]) => void
2122
}
2223

2324
interface DropdownMenuItemComponentProps {
@@ -44,6 +45,7 @@ export const DropdownMenu: FunctionComponent<DropdownMenuProps> = ({
4445
items,
4546
side,
4647
align,
48+
onSelect
4749
}) => {
4850
return (
4951
<RadixDropdownMenu.Root>

packages/ui/components/VisualEditor.tsx

Lines changed: 139 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import React, { useState, useEffect } from 'react';
22
import SchemaObject from './VisualEditor/SchemaObject';
33
import _ from 'lodash';
4+
import { getColorForType } from './VisualEditor/SchemaProperty';
5+
import { DropdownMenu, DropdownMenuItem } from './DropdownMenu';
6+
import { IoIosArrowDropdown } from 'react-icons/io';
47

58
interface VisualEditorProps {
69
schema: string;
@@ -15,14 +18,7 @@ interface SchemaObjectInterface {
1518
}
1619

1720
export const VisualEditor: React.FC<VisualEditorProps> = ({ schema, onSchemaChange }) => {
18-
const selectStyle = {
19-
backgroundColor: '#0F172A',
20-
color: 'white',
21-
borderRadius: '3px',
22-
fontSize: '12px',
23-
fontFamily: 'Inter, sans-serif'
24-
};
25-
21+
2622
const [schemaObject, setSchemaObject] = useState<SchemaObjectInterface>({});
2723

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

3834
const handleSchemaChange = (updatedPart: any) => {
39-
const updatedSchema = { ...schemaObject, ...updatedPart };
35+
let updatedSchema = _.cloneDeep(schemaObject);
36+
console.log("updatedPart.type", updatedPart.type)
37+
38+
if (updatedSchema.type === 'array') {
39+
if (updatedPart.items && updatedPart.items.type === 'object') {
40+
updatedSchema.items = updatedPart.items;
41+
} else if (updatedPart?.items?.properties) {
42+
updatedSchema.items = {
43+
...updatedSchema.items,
44+
properties: {
45+
...updatedSchema.items.properties,
46+
...updatedPart.items.properties,
47+
},
48+
};
49+
} else if (updatedPart.type !== 'object' ) {
50+
updatedSchema = { ...schemaObject, ...updatedPart };
51+
} else if (Object.keys(updatedPart.properties).length === 0 && updatedPart?.required === undefined){
52+
updatedSchema = { ...schemaObject, ...updatedPart };
53+
} else {
54+
updatedSchema.items = { ...updatedSchema.items, ...updatedPart };
55+
}
56+
} else {
57+
updatedSchema = { ...schemaObject, ...updatedPart };
58+
}
59+
4060
const newSchemaString = JSON.stringify(updatedSchema);
4161
console.log('Schema updated:', newSchemaString);
4262
setSchemaObject(updatedSchema);
4363
onSchemaChange(newSchemaString);
4464
};
4565

46-
const renderRootTypeSelector = () => (
47-
<div>
48-
<select
49-
value={schemaObject.type || ''}
50-
onChange={(e) => handleSchemaChange({ type: e.target.value })}
51-
style={selectStyle}
52-
>
53-
<option value="">Select root type</option>
54-
<option value="object">Object</option>
55-
<option value="array">Array</option>
56-
<option value="string">String</option>
57-
<option value="number">Number</option>
58-
<option value="boolean">Boolean</option>
59-
</select>
60-
</div>
61-
);
66+
const handleRootTypeDropdownSelect = (selectedOption: string) => {
67+
if (selectedOption === 'array') {
68+
handleSchemaChange({ type: 'array', items: { type: 'string' }, properties: undefined, required: undefined});
69+
} else {
70+
handleSchemaChange({ type: selectedOption, properties: undefined, required: undefined});
71+
}
72+
};
73+
74+
const handleArrayItemTypeDropdownSelect = (selectedOption: string) => {
75+
handleSchemaChange({ items: schemaObject.items ? { ...schemaObject.items, type: selectedOption } : { type: selectedOption } });
76+
};
77+
78+
const rootTypeOptions: DropdownMenuItem[] = [
79+
{ title: 'Select Root Type',onSelect: () => {}},
80+
{ type: 'separator'},
81+
{ type: 'regular', title: 'Object', onSelect: () => handleRootTypeDropdownSelect('object') },
82+
{ type: 'regular', title: 'Array', onSelect: () => handleRootTypeDropdownSelect('array') },
83+
{ type: 'regular', title: 'String', onSelect: () => handleRootTypeDropdownSelect('string') },
84+
{ type: 'regular', title: 'Number', onSelect: () => handleRootTypeDropdownSelect('number') },
85+
{ type: 'regular', title: 'Boolean', onSelect: () => handleRootTypeDropdownSelect('boolean') },
86+
];
87+
88+
const itemTypeOptions: DropdownMenuItem[] = [
89+
{ title: 'Select Items Type',onSelect: () => {}},
90+
{ type: 'separator'},
91+
{ type: 'regular', title: 'String', onSelect: () => handleArrayItemTypeDropdownSelect('string') },
92+
{ type: 'regular', title: 'Number', onSelect: () => handleArrayItemTypeDropdownSelect('number') },
93+
{ type: 'regular', title: 'Boolean', onSelect: () => handleArrayItemTypeDropdownSelect('boolean') },
94+
{ type: 'regular', title: 'Object', onSelect: () => handleArrayItemTypeDropdownSelect('object') },
95+
];
6296

63-
const renderArrayItemTypeSelector = () => {
64-
if (schemaObject.type === 'array') {
97+
const renderRootTypeDisplay = () => {
98+
if(schemaObject.type === 'array') {
99+
return null;
100+
}
101+
const rootType = schemaObject.type || '';
102+
const displayRootType = rootType.charAt(0).toUpperCase() + rootType.slice(1);
103+
return (
104+
<div className="flex items-center">
105+
<span
106+
style={{
107+
color: getColorForType(rootType),
108+
borderRadius: '3px',
109+
padding: '2px 4px',
110+
fontSize: '14px',
111+
fontFamily: 'Inter, Helvetica',
112+
}}
113+
>
114+
{displayRootType}
115+
</span>
116+
</div>
117+
);
118+
};
119+
120+
const renderArrayItemTypeDisplay = () => {
121+
if (schemaObject.type === 'array' && schemaObject.items) {
122+
const itemType = schemaObject.items?.type || '';
65123
return (
66-
<div>
67-
<strong>Array Item Type:</strong>
68-
<select
69-
value={schemaObject.items?.type || ''}
70-
onChange={(e) => handleSchemaChange({ items: { ...schemaObject.items, type: e.target.value } })}
71-
style={selectStyle}
124+
<div className="flex items-center">
125+
<span
126+
style={{
127+
color: getColorForType('array', itemType),
128+
borderRadius: '3px',
129+
padding: '2px 4px',
130+
fontSize: '14px',
131+
fontFamily: 'Inter, Helvetica',
132+
}}
72133
>
73-
<option value="">Select item type</option>
74-
<option value="string">String</option>
75-
<option value="number">Number</option>
76-
<option value="boolean">Boolean</option>
77-
<option value="object">Object</option>
78-
<option value="array">Array</option>
79-
</select>
134+
{`Array<${itemType}>`}
135+
</span>
80136
</div>
81137
);
82138
}
83139
return null;
84140
};
85141

86142
return (
87-
<div className="visual-editor" style={{ width: '45vw', minWidth: '550px', background: '#0F172A', color: '#CBD5E1', fontFamily: 'Inter, sans-serif', padding: '20px'}}>
88-
{renderRootTypeSelector()}
89-
{renderArrayItemTypeSelector()}
90-
91-
<SchemaObject
92-
schema={schemaObject.type === 'array' ? schemaObject.items : schemaObject}
93-
onSchemaChange={(newSchema: any) => handleSchemaChange(newSchema)}
94-
path={schemaObject.type === 'array' ? 'items' : ''}
95-
level={0}
96-
/>
143+
<div
144+
className="visual-editor"
145+
style={{
146+
width: "45vw",
147+
minWidth: "550px",
148+
background: "#0F172A",
149+
color: "#CBD5E1",
150+
fontFamily: "Inter, sans-serif",
151+
padding: "20px",
152+
}}
153+
>
154+
<div className="flex items-center gap-2">
155+
{renderRootTypeDisplay()}
156+
{renderArrayItemTypeDisplay()}
157+
<DropdownMenu
158+
trigger={
159+
<button>
160+
<IoIosArrowDropdown />
161+
</button>
162+
}
163+
items={rootTypeOptions}
164+
/>
165+
{schemaObject.type === "array" && (
166+
<DropdownMenu
167+
trigger={
168+
<button>
169+
<IoIosArrowDropdown />
170+
</button>
171+
}
172+
items={itemTypeOptions}
173+
/>
174+
)}
175+
</div>
176+
{(schemaObject.type === "object" || (schemaObject.type === "array" && schemaObject.items.type === "object")) && (
177+
<SchemaObject
178+
schema={
179+
schemaObject.type === "array" ? schemaObject.items : schemaObject
180+
}
181+
onSchemaChange={(newSchema: any) => handleSchemaChange(newSchema)}
182+
path={schemaObject.type === "array" ? "items" : ""}
183+
level={0}
184+
/>
185+
)}
97186
</div>
98187
);
99188
};

packages/ui/components/VisualEditor/CodeEditor.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ interface CodeEditorProps {
88
}
99

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

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

3838
return (
3939
<div className="code-editor">
40-
<h2>Code Editor</h2>
4140
{error && <p style={{ color: 'red' }}>Error: {error}</p>}
4241
<textarea
4342
value={value}

0 commit comments

Comments
 (0)