Skip to content

Commit c91f70c

Browse files
authored
feat(material): json-schema-editor optimize (bytedance#662)
* feat(material): json-schema-editor optimize * feat: remove useless config.json * feat: print install script
1 parent 7bae7c3 commit c91f70c

File tree

9 files changed

+81
-269
lines changed

9 files changed

+81
-269
lines changed

packages/materials/form-materials/bin/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ program
7272
let { packagesToInstall } = copyMaterial(material, projectInfo);
7373

7474
// 4. Install the dependencies
75-
packagesToInstall.push(`@flowgram.ai/editor`);
7675
packagesToInstall = packagesToInstall.map((_pkg) => {
7776
if (
7877
_pkg.startsWith(`@flowgram.ai/`) &&

packages/materials/form-materials/bin/project.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,22 +70,26 @@ export function findRushJson(startPath: string): string | null {
7070

7171
export function installDependencies(packages: string[], projectInfo: ProjectInfo): void {
7272
if (fs.existsSync(path.join(projectInfo.projectPath, 'yarn.lock'))) {
73+
console.log(`yarn add ${packages.join(' ')}`);
7374
execSync(`yarn add ${packages.join(' ')}`, { stdio: 'inherit' });
7475
return;
7576
}
7677

7778
if (fs.existsSync(path.join(projectInfo.projectPath, 'pnpm-lock.yaml'))) {
79+
console.log(`pnpm add ${packages.join(' ')}`);
7880
execSync(`pnpm add ${packages.join(' ')}`, { stdio: 'inherit' });
7981
return;
8082
}
8183

8284
// rush monorepo
8385
if (findRushJson(projectInfo.projectPath)) {
86+
console.log(`rush add ${packages.map((pkg) => `--package ${pkg}`).join(' ')}`);
8487
execSync(`rush add ${packages.map((pkg) => `--package ${pkg}`).join(' ')}`, {
8588
stdio: 'inherit',
8689
});
8790
return;
8891
}
8992

93+
console.log(`npm install ${packages.join(' ')}`);
9094
execSync(`npm install ${packages.join(' ')}`, { stdio: 'inherit' });
9195
}

packages/materials/form-materials/src/components/json-schema-editor/default-value.tsx

Lines changed: 9 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,19 @@
33
* SPDX-License-Identifier: MIT
44
*/
55

6-
import React, { useRef, useState, useCallback } from 'react';
6+
import React from 'react';
77

88
import { IJsonSchema } from '@flowgram.ai/json-schema';
9-
import { IconButton, JsonViewer, Tooltip } from '@douyinfe/semi-ui';
10-
import { IconBrackets } from '@douyinfe/semi-icons';
9+
import { I18n } from '@flowgram.ai/editor';
1110

1211
import { ConstantInput } from '@/components/constant-input';
1312

14-
import { getValueType } from './utils';
15-
import {
16-
ConstantInputWrapper,
17-
JSONHeader,
18-
JSONHeaderLeft,
19-
JSONHeaderRight,
20-
JSONViewerWrapper,
21-
} from './styles';
13+
import { ConstantInputWrapper } from './styles';
2214

2315
/**
24-
* 根据不同的数据类型渲染对应的默认值输入组件。
25-
* @param props - 组件属性,包括 value, type, placeholder, onChange
26-
* @returns 返回对应类型的输入组件或 null
16+
* Renders the corresponding default value input component based on different data types.
17+
* @param props - Component properties, including value, type, placeholder, onChange.
18+
* @returns Returns the input component of the corresponding type or null.
2719
*/
2820
export function DefaultValue(props: {
2921
value: any;
@@ -34,102 +26,15 @@ export function DefaultValue(props: {
3426
jsonFormatText?: string;
3527
onChange: (value: any) => void;
3628
}) {
37-
const { value, schema, type, onChange, placeholder, jsonFormatText } = props;
29+
const { value, schema, onChange, placeholder } = props;
3830

39-
const wrapperRef = useRef<HTMLDivElement>(null);
40-
const JsonViewerRef = useRef<JsonViewer>(null);
41-
42-
// 为 JsonViewer 添加状态管理
43-
const [internalJsonValue, setInternalJsonValue] = useState<string>(
44-
getValueType(value) === 'string' ? value : ''
45-
);
46-
47-
// 使用 useCallback 创建稳定的回调函数
48-
const handleJsonChange = useCallback((val: string) => {
49-
// 只在值真正改变时才更新状态
50-
if (val !== internalJsonValue) {
51-
setInternalJsonValue(val);
52-
}
53-
}, []);
54-
55-
// 处理编辑完成事件
56-
const handleEditComplete = useCallback(() => {
57-
// 只有当存在key,编辑完成时才触发父组件的 onChange
58-
onChange(internalJsonValue);
59-
// 确保在更新后移除焦点
60-
requestAnimationFrame(() => {
61-
// JsonViewerRef.current?.format();
62-
wrapperRef.current?.blur();
63-
});
64-
setJsonReadOnly(true);
65-
}, [internalJsonValue, onChange]);
66-
67-
const [jsonReadOnly, setJsonReadOnly] = useState<boolean>(true);
68-
69-
const handleFormatJson = useCallback(() => {
70-
try {
71-
const parsed = JSON.parse(internalJsonValue);
72-
const formatted = JSON.stringify(parsed, null, 4);
73-
setInternalJsonValue(formatted);
74-
onChange(formatted);
75-
} catch (error) {
76-
console.error('Invalid JSON:', error);
77-
}
78-
}, [internalJsonValue, onChange]);
79-
80-
return type === 'object' ? (
81-
<>
82-
<JSONHeader>
83-
<JSONHeaderLeft>json</JSONHeaderLeft>
84-
<JSONHeaderRight>
85-
<Tooltip content={jsonFormatText ?? 'Format'}>
86-
<IconButton
87-
icon={<IconBrackets style={{ color: 'var(--semi-color-primary)' }} />}
88-
size="small"
89-
type="tertiary"
90-
theme="borderless"
91-
onClick={handleFormatJson}
92-
/>
93-
</Tooltip>
94-
</JSONHeaderRight>
95-
</JSONHeader>
96-
97-
<JSONViewerWrapper
98-
ref={wrapperRef}
99-
tabIndex={-1}
100-
onBlur={(e) => {
101-
if (wrapperRef.current && !wrapperRef.current?.contains(e.relatedTarget as Node)) {
102-
handleEditComplete();
103-
}
104-
}}
105-
onClick={(e: React.MouseEvent) => {
106-
setJsonReadOnly(false);
107-
}}
108-
>
109-
<JsonViewer
110-
ref={JsonViewerRef}
111-
value={getValueType(value) === 'string' ? value : ''}
112-
height={120}
113-
width="100%"
114-
showSearch={false}
115-
options={{
116-
readOnly: jsonReadOnly,
117-
formatOptions: { tabSize: 4, insertSpaces: true, eol: '\n' },
118-
}}
119-
style={{
120-
padding: 0,
121-
}}
122-
onChange={handleJsonChange}
123-
/>
124-
</JSONViewerWrapper>
125-
</>
126-
) : (
31+
return (
12732
<ConstantInputWrapper>
12833
<ConstantInput
12934
value={value}
13035
onChange={(_v) => onChange(_v)}
13136
schema={schema || { type: 'string' }}
132-
placeholder={placeholder ?? 'Default value if parameter is not provided'}
37+
placeholder={placeholder ?? I18n.t('Default value if parameter is not provided')}
13338
/>
13439
</ConstantInputWrapper>
13540
);

packages/materials/form-materials/src/components/json-schema-editor/hooks.tsx

Lines changed: 53 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
* SPDX-License-Identifier: MIT
44
*/
55

6-
import { useEffect, useMemo, useRef, useState } from 'react';
6+
import { useEffect, useState } from 'react';
77

8-
import { omit } from 'lodash';
9-
import { IJsonSchema } from '@flowgram.ai/json-schema';
8+
import { difference, omit } from 'lodash';
9+
import { produce } from 'immer';
10+
import { IJsonSchema, type JsonSchemaTypeManager, useTypeManager } from '@flowgram.ai/json-schema';
1011

1112
import { PropertyValueType } from './types';
1213

@@ -15,92 +16,46 @@ function genId() {
1516
return _id++;
1617
}
1718

18-
function getDrilldownSchema(
19-
value?: PropertyValueType,
20-
path?: (keyof PropertyValueType)[]
21-
): { schema?: PropertyValueType | null; path?: (keyof PropertyValueType)[] } {
22-
if (!value) {
23-
return {};
24-
}
25-
26-
if (value.type === 'array' && value.items) {
27-
return getDrilldownSchema(value.items, [...(path || []), 'items']);
28-
}
29-
30-
return { schema: value, path };
31-
}
32-
3319
export function usePropertiesEdit(
3420
value?: PropertyValueType,
3521
onChange?: (value: PropertyValueType) => void
3622
) {
37-
// Get drilldown (array.items.items...)
38-
const drilldown = useMemo(() => getDrilldownSchema(value), [value, value?.type, value?.items]);
39-
40-
const isDrilldownObject = drilldown.schema?.type === 'object';
41-
42-
// Generate Init Property List
43-
const initPropertyList = useMemo(
44-
() =>
45-
isDrilldownObject
46-
? Object.entries(drilldown.schema?.properties || {})
47-
.sort(([, a], [, b]) => (a.extra?.index ?? 0) - (b.extra?.index ?? 0))
48-
.map(
49-
([name, _value], index) =>
50-
({
51-
key: genId(),
52-
name,
53-
isPropertyRequired: drilldown.schema?.required?.includes(name) || false,
54-
..._value,
55-
extra: {
56-
...(_value.extra || {}),
57-
index,
58-
},
59-
} as PropertyValueType)
60-
)
61-
: [],
62-
[isDrilldownObject]
63-
);
64-
65-
const [propertyList, setPropertyList] = useState<PropertyValueType[]>(initPropertyList);
66-
67-
const mountRef = useRef(false);
23+
const typeManager = useTypeManager() as JsonSchemaTypeManager;
24+
25+
// Get drilldown properties (array.items.items.properties...)
26+
const drilldownSchema = typeManager.getPropertiesParent(value || {});
27+
const canAddField = typeManager.canAddField(value || {});
28+
29+
const [propertyList, setPropertyList] = useState<PropertyValueType[]>([]);
6830

6931
useEffect(() => {
70-
// If initRef is true, it means the component has been mounted
71-
if (mountRef.current) {
72-
// If the value is changed, update the property list
73-
setPropertyList((_list) => {
74-
const nameMap = new Map<string, PropertyValueType>();
75-
76-
for (const _property of _list) {
77-
if (_property.name) {
78-
nameMap.set(_property.name, _property);
79-
}
80-
}
81-
return Object.entries(drilldown.schema?.properties || {})
82-
.sort(([, a], [, b]) => (a.extra?.index ?? 0) - (b.extra?.index ?? 0))
83-
.map(([name, _value]) => {
84-
const _property = nameMap.get(name);
85-
if (_property) {
86-
return {
87-
key: _property.key,
88-
name,
89-
isPropertyRequired: drilldown.schema?.required?.includes(name) || false,
90-
..._value,
91-
};
92-
}
93-
return {
94-
key: genId(),
95-
name,
96-
isPropertyRequired: drilldown.schema?.required?.includes(name) || false,
97-
..._value,
98-
};
99-
});
100-
});
101-
}
102-
mountRef.current = true;
103-
}, [drilldown.schema]);
32+
// If the value is changed, update the property list
33+
setPropertyList((_list) => {
34+
const newNames = Object.entries(drilldownSchema?.properties || {})
35+
.sort(([, a], [, b]) => (a.extra?.index ?? 0) - (b.extra?.index ?? 0))
36+
.map(([key]) => key);
37+
38+
const oldNames = _list.map((item) => item.name).filter(Boolean) as string[];
39+
const addNames = difference(newNames, oldNames);
40+
41+
return _list
42+
.filter((item) => !item.name || newNames.includes(item.name))
43+
.map((item) => ({
44+
key: item.key,
45+
name: item.name,
46+
isPropertyRequired: drilldownSchema?.required?.includes(item.name || '') || false,
47+
...item,
48+
}))
49+
.concat(
50+
addNames.map((_name) => ({
51+
key: genId(),
52+
name: _name,
53+
isPropertyRequired: drilldownSchema?.required?.includes(_name) || false,
54+
...(drilldownSchema?.properties?.[_name] || {}),
55+
}))
56+
);
57+
});
58+
}, [drilldownSchema]);
10459

10560
const updatePropertyList = (updater: (list: PropertyValueType[]) => PropertyValueType[]) => {
10661
setPropertyList((_list) => {
@@ -122,21 +77,25 @@ export function usePropertiesEdit(
12277
}
12378
}
12479

125-
let drilldownSchema = value || {};
126-
if (drilldown.path) {
127-
drilldownSchema = drilldown.path.reduce((acc, key) => acc[key], value || {});
128-
}
129-
drilldownSchema.properties = nextProperties;
130-
drilldownSchema.required = nextRequired;
80+
onChange?.(
81+
produce(value || {}, (draft) => {
82+
const propertiesParent = typeManager.getPropertiesParent(draft);
13183

132-
onChange?.(value || {});
84+
if (propertiesParent) {
85+
propertiesParent.properties = nextProperties;
86+
propertiesParent.required = nextRequired;
87+
return;
88+
}
89+
})
90+
);
13391

13492
return next;
13593
});
13694
};
13795

13896
const onAddProperty = () => {
139-
updatePropertyList((_list) => [
97+
// set property list only, not trigger updatePropertyList
98+
setPropertyList((_list) => [
14099
..._list,
141100
{ key: genId(), name: '', type: 'string', extra: { index: _list.length + 1 } },
142101
]);
@@ -153,14 +112,14 @@ export function usePropertiesEdit(
153112
};
154113

155114
useEffect(() => {
156-
if (!isDrilldownObject) {
115+
if (!canAddField) {
157116
setPropertyList([]);
158117
}
159-
}, [isDrilldownObject]);
118+
}, [canAddField]);
160119

161120
return {
162121
propertyList,
163-
isDrilldownObject,
122+
canAddField,
164123
onAddProperty,
165124
onRemoveProperty,
166125
onEditProperty,

0 commit comments

Comments
 (0)