@flowgram.ai/form-materials 0.1.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/index.ts +99 -0
- package/bin/materials.ts +156 -0
- package/bin/project.ts +91 -0
- package/dist/esm/index.js +3303 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/index.d.mts +495 -0
- package/dist/index.d.ts +495 -0
- package/dist/index.js +3299 -0
- package/dist/index.js.map +1 -0
- package/package.json +75 -0
- package/src/components/batch-outputs/config.json +12 -0
- package/src/components/batch-outputs/index.tsx +61 -0
- package/src/components/batch-outputs/styles.tsx +19 -0
- package/src/components/batch-outputs/types.ts +22 -0
- package/src/components/batch-outputs/use-list.ts +86 -0
- package/src/components/batch-variable-selector/config.json +5 -0
- package/src/components/batch-variable-selector/index.tsx +24 -0
- package/src/components/code-editor/config.json +9 -0
- package/src/components/code-editor/index.tsx +74 -0
- package/src/components/code-editor/language-features.ts +24 -0
- package/src/components/code-editor/theme/dark.ts +119 -0
- package/src/components/code-editor/theme/index.ts +12 -0
- package/src/components/code-editor/theme/light.ts +119 -0
- package/src/components/code-editor/utils.ts +20 -0
- package/src/components/condition-row/config.json +5 -0
- package/src/components/condition-row/constants.ts +128 -0
- package/src/components/condition-row/hooks/useOp.tsx +50 -0
- package/src/components/condition-row/hooks/useRule.ts +31 -0
- package/src/components/condition-row/index.tsx +81 -0
- package/src/components/condition-row/styles.tsx +30 -0
- package/src/components/condition-row/types.ts +42 -0
- package/src/components/constant-input/config.json +6 -0
- package/src/components/constant-input/index.tsx +86 -0
- package/src/components/constant-input/types.ts +23 -0
- package/src/components/dynamic-value-input/config.json +5 -0
- package/src/components/dynamic-value-input/index.tsx +92 -0
- package/src/components/dynamic-value-input/styles.tsx +26 -0
- package/src/components/index.ts +18 -0
- package/src/components/json-editor-with-variables/config.json +13 -0
- package/src/components/json-editor-with-variables/extensions/variable-tag.tsx +173 -0
- package/src/components/json-editor-with-variables/extensions/variable-tree.tsx +83 -0
- package/src/components/json-editor-with-variables/index.tsx +19 -0
- package/src/components/json-editor-with-variables/styles.tsx +44 -0
- package/src/components/json-schema-editor/components/blur-input.tsx +27 -0
- package/src/components/json-schema-editor/config.json +13 -0
- package/src/components/json-schema-editor/default-value.tsx +135 -0
- package/src/components/json-schema-editor/hooks.tsx +166 -0
- package/src/components/json-schema-editor/index.tsx +267 -0
- package/src/components/json-schema-editor/styles.tsx +240 -0
- package/src/components/json-schema-editor/types.ts +26 -0
- package/src/components/json-schema-editor/utils.ts +29 -0
- package/src/components/prompt-editor/config.json +9 -0
- package/src/components/prompt-editor/extensions/jinja.tsx +58 -0
- package/src/components/prompt-editor/extensions/language-support.tsx +19 -0
- package/src/components/prompt-editor/extensions/markdown.tsx +75 -0
- package/src/components/prompt-editor/index.tsx +68 -0
- package/src/components/prompt-editor/styles.tsx +18 -0
- package/src/components/prompt-editor/types.tsx +18 -0
- package/src/components/prompt-editor-with-inputs/config.json +13 -0
- package/src/components/prompt-editor-with-inputs/extensions/inputs-tree.tsx +82 -0
- package/src/components/prompt-editor-with-inputs/index.tsx +22 -0
- package/src/components/prompt-editor-with-inputs/inputs-picker.tsx +100 -0
- package/src/components/prompt-editor-with-variables/config.json +13 -0
- package/src/components/prompt-editor-with-variables/extensions/variable-tag.tsx +179 -0
- package/src/components/prompt-editor-with-variables/extensions/variable-tree.tsx +83 -0
- package/src/components/prompt-editor-with-variables/index.tsx +19 -0
- package/src/components/prompt-editor-with-variables/styles.tsx +44 -0
- package/src/components/type-selector/config.json +5 -0
- package/src/components/type-selector/constants.tsx +364 -0
- package/src/components/type-selector/index.tsx +62 -0
- package/src/components/variable-selector/config.json +5 -0
- package/src/components/variable-selector/index.tsx +116 -0
- package/src/components/variable-selector/styles.tsx +59 -0
- package/src/components/variable-selector/use-variable-tree.tsx +103 -0
- package/src/effects/auto-rename-ref/config.json +5 -0
- package/src/effects/auto-rename-ref/index.ts +109 -0
- package/src/effects/index.ts +10 -0
- package/src/effects/provide-batch-input/config.json +5 -0
- package/src/effects/provide-batch-input/index.ts +43 -0
- package/src/effects/provide-batch-outputs/config.json +5 -0
- package/src/effects/provide-batch-outputs/index.ts +38 -0
- package/src/effects/provide-json-schema-outputs/config.json +8 -0
- package/src/effects/provide-json-schema-outputs/index.ts +28 -0
- package/src/effects/sync-variable-title/config.json +5 -0
- package/src/effects/sync-variable-title/index.ts +28 -0
- package/src/form-plugins/batch-outputs-plugin/config.json +7 -0
- package/src/form-plugins/batch-outputs-plugin/index.ts +104 -0
- package/src/form-plugins/index.ts +6 -0
- package/src/index.ts +10 -0
- package/src/typings/flow-value/config.json +5 -0
- package/src/typings/flow-value/index.ts +32 -0
- package/src/typings/index.ts +7 -0
- package/src/typings/json-schema/config.json +5 -0
- package/src/typings/json-schema/index.ts +36 -0
- package/src/utils/format-legacy-refs/config.json +5 -0
- package/src/utils/format-legacy-refs/index.ts +158 -0
- package/src/utils/format-legacy-refs/readme.md +38 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/json-schema/config.json +5 -0
- package/src/utils/json-schema/index.ts +180 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useLayoutEffect } from 'react';
|
|
7
|
+
|
|
8
|
+
import { createRoot, Root } from 'react-dom/client';
|
|
9
|
+
import { isEqual, last } from 'lodash';
|
|
10
|
+
import {
|
|
11
|
+
BaseVariableField,
|
|
12
|
+
Disposable,
|
|
13
|
+
DisposableCollection,
|
|
14
|
+
Scope,
|
|
15
|
+
useCurrentScope,
|
|
16
|
+
} from '@flowgram.ai/editor';
|
|
17
|
+
import { Popover } from '@douyinfe/semi-ui';
|
|
18
|
+
import { IconIssueStroked } from '@douyinfe/semi-icons';
|
|
19
|
+
import { useInjector } from '@coze-editor/editor/react';
|
|
20
|
+
import {
|
|
21
|
+
Decoration,
|
|
22
|
+
DecorationSet,
|
|
23
|
+
EditorView,
|
|
24
|
+
MatchDecorator,
|
|
25
|
+
ViewPlugin,
|
|
26
|
+
WidgetType,
|
|
27
|
+
} from '@codemirror/view';
|
|
28
|
+
|
|
29
|
+
import { UIPopoverContent, UIRootTitle, UITag, UIVarName } from '../styles';
|
|
30
|
+
|
|
31
|
+
class VariableTagWidget extends WidgetType {
|
|
32
|
+
keyPath?: string[];
|
|
33
|
+
|
|
34
|
+
toDispose = new DisposableCollection();
|
|
35
|
+
|
|
36
|
+
scope: Scope;
|
|
37
|
+
|
|
38
|
+
root: Root;
|
|
39
|
+
|
|
40
|
+
constructor({ keyPath, scope }: { keyPath?: string[]; scope: Scope }) {
|
|
41
|
+
super();
|
|
42
|
+
|
|
43
|
+
this.keyPath = keyPath;
|
|
44
|
+
this.scope = scope;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
renderIcon = (icon: string | JSX.Element) => {
|
|
48
|
+
if (typeof icon === 'string') {
|
|
49
|
+
return <img style={{ marginRight: 8 }} width={12} height={12} src={icon} />;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return icon;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
renderVariable(v?: BaseVariableField) {
|
|
56
|
+
if (!v) {
|
|
57
|
+
this.root.render(
|
|
58
|
+
<UITag prefixIcon={<IconIssueStroked />} color="amber">
|
|
59
|
+
Unknown
|
|
60
|
+
</UITag>
|
|
61
|
+
);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const rootField = last(v.parentFields);
|
|
66
|
+
|
|
67
|
+
const rootTitle = (
|
|
68
|
+
<UIRootTitle>{rootField?.meta.title ? `${rootField.meta.title} -` : ''}</UIRootTitle>
|
|
69
|
+
);
|
|
70
|
+
const rootIcon = this.renderIcon(rootField?.meta.icon);
|
|
71
|
+
|
|
72
|
+
this.root.render(
|
|
73
|
+
<Popover
|
|
74
|
+
content={
|
|
75
|
+
<UIPopoverContent>
|
|
76
|
+
{rootIcon}
|
|
77
|
+
{rootTitle}
|
|
78
|
+
<UIVarName>{v?.keyPath.slice(1).join('.')}</UIVarName>
|
|
79
|
+
</UIPopoverContent>
|
|
80
|
+
}
|
|
81
|
+
>
|
|
82
|
+
<UITag prefixIcon={rootIcon}>
|
|
83
|
+
{rootTitle}
|
|
84
|
+
<UIVarName>{v?.key}</UIVarName>
|
|
85
|
+
</UITag>
|
|
86
|
+
</Popover>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
toDOM(view: EditorView): HTMLElement {
|
|
91
|
+
const dom = document.createElement('span');
|
|
92
|
+
|
|
93
|
+
this.root = createRoot(dom);
|
|
94
|
+
|
|
95
|
+
this.toDispose.push(
|
|
96
|
+
Disposable.create(() => {
|
|
97
|
+
this.root.unmount();
|
|
98
|
+
})
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
this.toDispose.push(
|
|
102
|
+
this.scope.available.trackByKeyPath(
|
|
103
|
+
this.keyPath,
|
|
104
|
+
(v) => {
|
|
105
|
+
this.renderVariable(v);
|
|
106
|
+
},
|
|
107
|
+
{ triggerOnInit: false }
|
|
108
|
+
)
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
this.renderVariable(this.scope.available.getByKeyPath(this.keyPath));
|
|
112
|
+
|
|
113
|
+
return dom;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
eq(other: VariableTagWidget) {
|
|
117
|
+
return isEqual(this.keyPath, other.keyPath);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
ignoreEvent(): boolean {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
destroy(dom: HTMLElement): void {
|
|
125
|
+
this.toDispose.dispose();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function VariableTagInject() {
|
|
130
|
+
const injector = useInjector();
|
|
131
|
+
|
|
132
|
+
const scope = useCurrentScope();
|
|
133
|
+
|
|
134
|
+
// 基于 {{var}} 的正则进行匹配,匹配后进行自定义渲染
|
|
135
|
+
useLayoutEffect(() => {
|
|
136
|
+
const atMatcher = new MatchDecorator({
|
|
137
|
+
regexp: /\{\{([^\}]+)\}\}/g,
|
|
138
|
+
decoration: (match) =>
|
|
139
|
+
Decoration.replace({
|
|
140
|
+
widget: new VariableTagWidget({
|
|
141
|
+
keyPath: match[1]?.split('.') ?? [],
|
|
142
|
+
scope,
|
|
143
|
+
}),
|
|
144
|
+
}),
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
return injector.inject([
|
|
148
|
+
ViewPlugin.fromClass(
|
|
149
|
+
class {
|
|
150
|
+
decorations: DecorationSet;
|
|
151
|
+
|
|
152
|
+
constructor(private view: EditorView) {
|
|
153
|
+
this.decorations = atMatcher.createDeco(view);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
update() {
|
|
157
|
+
this.decorations = atMatcher.createDeco(this.view);
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
decorations: (p) => p.decorations,
|
|
162
|
+
provide(p) {
|
|
163
|
+
return EditorView.atomicRanges.of(
|
|
164
|
+
(view) => view.plugin(p)?.decorations ?? Decoration.none
|
|
165
|
+
);
|
|
166
|
+
},
|
|
167
|
+
}
|
|
168
|
+
),
|
|
169
|
+
]);
|
|
170
|
+
}, [injector]);
|
|
171
|
+
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useEffect, useState } from 'react';
|
|
7
|
+
|
|
8
|
+
import { Popover, Tree } from '@douyinfe/semi-ui';
|
|
9
|
+
import {
|
|
10
|
+
Mention,
|
|
11
|
+
MentionOpenChangeEvent,
|
|
12
|
+
getCurrentMentionReplaceRange,
|
|
13
|
+
useEditor,
|
|
14
|
+
PositionMirror,
|
|
15
|
+
} from '@coze-editor/editor/react';
|
|
16
|
+
import { EditorAPI } from '@coze-editor/editor/preset-prompt';
|
|
17
|
+
|
|
18
|
+
import { useVariableTree } from '../../variable-selector';
|
|
19
|
+
|
|
20
|
+
export function VariableTree() {
|
|
21
|
+
const [posKey, setPosKey] = useState('');
|
|
22
|
+
const [visible, setVisible] = useState(false);
|
|
23
|
+
const [position, setPosition] = useState(-1);
|
|
24
|
+
const editor = useEditor<EditorAPI>();
|
|
25
|
+
|
|
26
|
+
function insert(variablePath: string) {
|
|
27
|
+
const range = getCurrentMentionReplaceRange(editor.$view.state);
|
|
28
|
+
|
|
29
|
+
if (!range) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
editor.replaceText({
|
|
34
|
+
...range,
|
|
35
|
+
text: '{{' + variablePath + '}}',
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
setVisible(false);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function handleOpenChange(e: MentionOpenChangeEvent) {
|
|
42
|
+
setPosition(e.state.selection.main.head);
|
|
43
|
+
setVisible(e.value);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (!editor) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}, [editor, visible]);
|
|
51
|
+
|
|
52
|
+
const treeData = useVariableTree({});
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<>
|
|
56
|
+
<Mention triggerCharacters={['@']} onOpenChange={handleOpenChange} />
|
|
57
|
+
|
|
58
|
+
<Popover
|
|
59
|
+
visible={visible}
|
|
60
|
+
trigger="custom"
|
|
61
|
+
position="topLeft"
|
|
62
|
+
rePosKey={posKey}
|
|
63
|
+
content={
|
|
64
|
+
<div style={{ width: 300 }}>
|
|
65
|
+
<Tree
|
|
66
|
+
treeData={treeData}
|
|
67
|
+
onSelect={(v) => {
|
|
68
|
+
insert(v);
|
|
69
|
+
}}
|
|
70
|
+
/>
|
|
71
|
+
</div>
|
|
72
|
+
}
|
|
73
|
+
>
|
|
74
|
+
{/* PositionMirror allows the Popover to appear at the specified cursor position */}
|
|
75
|
+
<PositionMirror
|
|
76
|
+
position={position}
|
|
77
|
+
// When Doc scroll, update position
|
|
78
|
+
onChange={() => setPosKey(String(Math.random()))}
|
|
79
|
+
/>
|
|
80
|
+
</Popover>
|
|
81
|
+
</>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
|
|
8
|
+
import { VariableTree } from './extensions/variable-tree';
|
|
9
|
+
import { VariableTagInject } from './extensions/variable-tag';
|
|
10
|
+
import { CodeEditor, type CodeEditorPropsType } from '../code-editor';
|
|
11
|
+
|
|
12
|
+
export function JsonEditorWithVariables(props: Omit<CodeEditorPropsType, 'languageId'>) {
|
|
13
|
+
return (
|
|
14
|
+
<CodeEditor languageId="json" activeLinePlaceholder="Press '@' to Select variable" {...props}>
|
|
15
|
+
<VariableTree />
|
|
16
|
+
<VariableTagInject />
|
|
17
|
+
</CodeEditor>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import styled from 'styled-components';
|
|
7
|
+
import { Tag } from '@douyinfe/semi-ui';
|
|
8
|
+
|
|
9
|
+
export const UIRootTitle = styled.div`
|
|
10
|
+
margin-right: 4px;
|
|
11
|
+
min-width: 20px;
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
text-overflow: ellipsis;
|
|
14
|
+
white-space: nowrap;
|
|
15
|
+
color: var(--semi-color-text-2);
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
export const UIVarName = styled.div`
|
|
19
|
+
overflow: hidden;
|
|
20
|
+
text-overflow: ellipsis;
|
|
21
|
+
white-space: nowrap;
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
export const UITag = styled(Tag)`
|
|
25
|
+
display: inline-flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
justify-content: flex-start;
|
|
28
|
+
max-width: 300px;
|
|
29
|
+
|
|
30
|
+
& .semi-tag-content-center {
|
|
31
|
+
justify-content: flex-start;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
&.semi-tag {
|
|
35
|
+
margin: 0 5px;
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
export const UIPopoverContent = styled.div`
|
|
40
|
+
padding: 10px;
|
|
41
|
+
display: inline-flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
justify-content: flex-start;
|
|
44
|
+
`;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useEffect, useState } from 'react';
|
|
7
|
+
|
|
8
|
+
import Input, { InputProps } from '@douyinfe/semi-ui/lib/es/input';
|
|
9
|
+
|
|
10
|
+
export function BlurInput(props: InputProps) {
|
|
11
|
+
const [value, setValue] = useState('');
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
setValue(props.value as string);
|
|
15
|
+
}, [props.value]);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<Input
|
|
19
|
+
{...props}
|
|
20
|
+
value={value}
|
|
21
|
+
onChange={(value) => {
|
|
22
|
+
setValue(value);
|
|
23
|
+
}}
|
|
24
|
+
onBlur={(e) => props.onChange?.(value, e)}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useRef, useState, useCallback } from 'react';
|
|
7
|
+
|
|
8
|
+
import { IconButton, JsonViewer, Tooltip } from '@douyinfe/semi-ui';
|
|
9
|
+
import { IconBrackets } from '@douyinfe/semi-icons';
|
|
10
|
+
|
|
11
|
+
import { getValueType } from './utils';
|
|
12
|
+
import {
|
|
13
|
+
ConstantInputWrapper,
|
|
14
|
+
JSONHeader,
|
|
15
|
+
JSONHeaderLeft,
|
|
16
|
+
JSONHeaderRight,
|
|
17
|
+
JSONViewerWrapper,
|
|
18
|
+
} from './styles';
|
|
19
|
+
import { ConstantInput } from '../constant-input';
|
|
20
|
+
import { IJsonSchema } from '../../typings';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 根据不同的数据类型渲染对应的默认值输入组件。
|
|
24
|
+
* @param props - 组件属性,包括 value, type, placeholder, onChange。
|
|
25
|
+
* @returns 返回对应类型的输入组件或 null。
|
|
26
|
+
*/
|
|
27
|
+
export function DefaultValue(props: {
|
|
28
|
+
value: any;
|
|
29
|
+
schema?: IJsonSchema;
|
|
30
|
+
name?: string;
|
|
31
|
+
type?: string;
|
|
32
|
+
placeholder?: string;
|
|
33
|
+
jsonFormatText?: string;
|
|
34
|
+
onChange: (value: any) => void;
|
|
35
|
+
}) {
|
|
36
|
+
const { value, schema, type, onChange, placeholder, jsonFormatText } = props;
|
|
37
|
+
|
|
38
|
+
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
39
|
+
const JsonViewerRef = useRef<JsonViewer>(null);
|
|
40
|
+
|
|
41
|
+
// 为 JsonViewer 添加状态管理
|
|
42
|
+
const [internalJsonValue, setInternalJsonValue] = useState<string>(
|
|
43
|
+
getValueType(value) === 'string' ? value : ''
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// 使用 useCallback 创建稳定的回调函数
|
|
47
|
+
const handleJsonChange = useCallback((val: string) => {
|
|
48
|
+
// 只在值真正改变时才更新状态
|
|
49
|
+
if (val !== internalJsonValue) {
|
|
50
|
+
setInternalJsonValue(val);
|
|
51
|
+
}
|
|
52
|
+
}, []);
|
|
53
|
+
|
|
54
|
+
// 处理编辑完成事件
|
|
55
|
+
const handleEditComplete = useCallback(() => {
|
|
56
|
+
// 只有当存在key,编辑完成时才触发父组件的 onChange
|
|
57
|
+
onChange(internalJsonValue);
|
|
58
|
+
// 确保在更新后移除焦点
|
|
59
|
+
requestAnimationFrame(() => {
|
|
60
|
+
// JsonViewerRef.current?.format();
|
|
61
|
+
wrapperRef.current?.blur();
|
|
62
|
+
});
|
|
63
|
+
setJsonReadOnly(true);
|
|
64
|
+
}, [internalJsonValue, onChange]);
|
|
65
|
+
|
|
66
|
+
const [jsonReadOnly, setJsonReadOnly] = useState<boolean>(true);
|
|
67
|
+
|
|
68
|
+
const handleFormatJson = useCallback(() => {
|
|
69
|
+
try {
|
|
70
|
+
const parsed = JSON.parse(internalJsonValue);
|
|
71
|
+
const formatted = JSON.stringify(parsed, null, 4);
|
|
72
|
+
setInternalJsonValue(formatted);
|
|
73
|
+
onChange(formatted);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error('Invalid JSON:', error);
|
|
76
|
+
}
|
|
77
|
+
}, [internalJsonValue, onChange]);
|
|
78
|
+
|
|
79
|
+
return type === 'object' ? (
|
|
80
|
+
<>
|
|
81
|
+
<JSONHeader>
|
|
82
|
+
<JSONHeaderLeft>json</JSONHeaderLeft>
|
|
83
|
+
<JSONHeaderRight>
|
|
84
|
+
<Tooltip content={jsonFormatText ?? 'Format'}>
|
|
85
|
+
<IconButton
|
|
86
|
+
icon={<IconBrackets style={{ color: 'var(--semi-color-primary)' }} />}
|
|
87
|
+
size="small"
|
|
88
|
+
type="tertiary"
|
|
89
|
+
theme="borderless"
|
|
90
|
+
onClick={handleFormatJson}
|
|
91
|
+
/>
|
|
92
|
+
</Tooltip>
|
|
93
|
+
</JSONHeaderRight>
|
|
94
|
+
</JSONHeader>
|
|
95
|
+
|
|
96
|
+
<JSONViewerWrapper
|
|
97
|
+
ref={wrapperRef}
|
|
98
|
+
tabIndex={-1}
|
|
99
|
+
onBlur={(e) => {
|
|
100
|
+
if (wrapperRef.current && !wrapperRef.current?.contains(e.relatedTarget as Node)) {
|
|
101
|
+
handleEditComplete();
|
|
102
|
+
}
|
|
103
|
+
}}
|
|
104
|
+
onClick={(e: React.MouseEvent) => {
|
|
105
|
+
setJsonReadOnly(false);
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
108
|
+
<JsonViewer
|
|
109
|
+
ref={JsonViewerRef}
|
|
110
|
+
value={getValueType(value) === 'string' ? value : ''}
|
|
111
|
+
height={120}
|
|
112
|
+
width="100%"
|
|
113
|
+
showSearch={false}
|
|
114
|
+
options={{
|
|
115
|
+
readOnly: jsonReadOnly,
|
|
116
|
+
formatOptions: { tabSize: 4, insertSpaces: true, eol: '\n' },
|
|
117
|
+
}}
|
|
118
|
+
style={{
|
|
119
|
+
padding: 0,
|
|
120
|
+
}}
|
|
121
|
+
onChange={handleJsonChange}
|
|
122
|
+
/>
|
|
123
|
+
</JSONViewerWrapper>
|
|
124
|
+
</>
|
|
125
|
+
) : (
|
|
126
|
+
<ConstantInputWrapper>
|
|
127
|
+
<ConstantInput
|
|
128
|
+
value={value}
|
|
129
|
+
onChange={(_v) => onChange(_v)}
|
|
130
|
+
schema={schema || { type: 'string' }}
|
|
131
|
+
placeholder={placeholder ?? 'Default value if parameter is not provided'}
|
|
132
|
+
/>
|
|
133
|
+
</ConstantInputWrapper>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
7
|
+
|
|
8
|
+
import { IJsonSchema } from '../../typings';
|
|
9
|
+
import { PropertyValueType } from './types';
|
|
10
|
+
|
|
11
|
+
let _id = 0;
|
|
12
|
+
function genId() {
|
|
13
|
+
return _id++;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getDrilldownSchema(
|
|
17
|
+
value?: PropertyValueType,
|
|
18
|
+
path?: (keyof PropertyValueType)[]
|
|
19
|
+
): { schema?: PropertyValueType | null; path?: (keyof PropertyValueType)[] } {
|
|
20
|
+
if (!value) {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (value.type === 'array' && value.items) {
|
|
25
|
+
return getDrilldownSchema(value.items, [...(path || []), 'items']);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return { schema: value, path };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function usePropertiesEdit(
|
|
32
|
+
value?: PropertyValueType,
|
|
33
|
+
onChange?: (value: PropertyValueType) => void
|
|
34
|
+
) {
|
|
35
|
+
// Get drilldown (array.items.items...)
|
|
36
|
+
const drilldown = useMemo(() => getDrilldownSchema(value), [value, value?.type, value?.items]);
|
|
37
|
+
|
|
38
|
+
const isDrilldownObject = drilldown.schema?.type === 'object';
|
|
39
|
+
|
|
40
|
+
// Generate Init Property List
|
|
41
|
+
const initPropertyList = useMemo(
|
|
42
|
+
() =>
|
|
43
|
+
isDrilldownObject
|
|
44
|
+
? Object.entries(drilldown.schema?.properties || {})
|
|
45
|
+
.sort(([, a], [, b]) => (a.extra?.index ?? 0) - (b.extra?.index ?? 0))
|
|
46
|
+
.map(
|
|
47
|
+
([name, _value], index) =>
|
|
48
|
+
({
|
|
49
|
+
key: genId(),
|
|
50
|
+
name,
|
|
51
|
+
isPropertyRequired: drilldown.schema?.required?.includes(name) || false,
|
|
52
|
+
..._value,
|
|
53
|
+
extra: {
|
|
54
|
+
...(_value.extra || {}),
|
|
55
|
+
index,
|
|
56
|
+
},
|
|
57
|
+
} as PropertyValueType)
|
|
58
|
+
)
|
|
59
|
+
: [],
|
|
60
|
+
[isDrilldownObject]
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const [propertyList, setPropertyList] = useState<PropertyValueType[]>(initPropertyList);
|
|
64
|
+
|
|
65
|
+
const mountRef = useRef(false);
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
// If initRef is true, it means the component has been mounted
|
|
69
|
+
if (mountRef.current) {
|
|
70
|
+
// If the value is changed, update the property list
|
|
71
|
+
setPropertyList((_list) => {
|
|
72
|
+
const nameMap = new Map<string, PropertyValueType>();
|
|
73
|
+
|
|
74
|
+
for (const _property of _list) {
|
|
75
|
+
if (_property.name) {
|
|
76
|
+
nameMap.set(_property.name, _property);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return Object.entries(drilldown.schema?.properties || {})
|
|
80
|
+
.sort(([, a], [, b]) => (a.extra?.index ?? 0) - (b.extra?.index ?? 0))
|
|
81
|
+
.map(([name, _value]) => {
|
|
82
|
+
const _property = nameMap.get(name);
|
|
83
|
+
if (_property) {
|
|
84
|
+
return {
|
|
85
|
+
key: _property.key,
|
|
86
|
+
name,
|
|
87
|
+
isPropertyRequired: drilldown.schema?.required?.includes(name) || false,
|
|
88
|
+
..._value,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
key: genId(),
|
|
93
|
+
name,
|
|
94
|
+
isPropertyRequired: drilldown.schema?.required?.includes(name) || false,
|
|
95
|
+
..._value,
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
mountRef.current = true;
|
|
101
|
+
}, [drilldown.schema]);
|
|
102
|
+
|
|
103
|
+
const updatePropertyList = (updater: (list: PropertyValueType[]) => PropertyValueType[]) => {
|
|
104
|
+
setPropertyList((_list) => {
|
|
105
|
+
const next = updater(_list);
|
|
106
|
+
|
|
107
|
+
// onChange to parent
|
|
108
|
+
const nextProperties: Record<string, IJsonSchema> = {};
|
|
109
|
+
const nextRequired: string[] = [];
|
|
110
|
+
|
|
111
|
+
for (const _property of next) {
|
|
112
|
+
if (!_property.name) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
nextProperties[_property.name] = _property;
|
|
117
|
+
|
|
118
|
+
if (_property.isPropertyRequired) {
|
|
119
|
+
nextRequired.push(_property.name);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let drilldownSchema = value || {};
|
|
124
|
+
if (drilldown.path) {
|
|
125
|
+
drilldownSchema = drilldown.path.reduce((acc, key) => acc[key], value || {});
|
|
126
|
+
}
|
|
127
|
+
drilldownSchema.properties = nextProperties;
|
|
128
|
+
drilldownSchema.required = nextRequired;
|
|
129
|
+
|
|
130
|
+
onChange?.(value || {});
|
|
131
|
+
|
|
132
|
+
return next;
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const onAddProperty = () => {
|
|
137
|
+
updatePropertyList((_list) => [
|
|
138
|
+
..._list,
|
|
139
|
+
{ key: genId(), name: '', type: 'string', extra: { index: _list.length + 1 } },
|
|
140
|
+
]);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const onRemoveProperty = (key: number) => {
|
|
144
|
+
updatePropertyList((_list) => _list.filter((_property) => _property.key !== key));
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const onEditProperty = (key: number, nextValue: PropertyValueType) => {
|
|
148
|
+
updatePropertyList((_list) =>
|
|
149
|
+
_list.map((_property) => (_property.key === key ? nextValue : _property))
|
|
150
|
+
);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
if (!isDrilldownObject) {
|
|
155
|
+
setPropertyList([]);
|
|
156
|
+
}
|
|
157
|
+
}, [isDrilldownObject]);
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
propertyList,
|
|
161
|
+
isDrilldownObject,
|
|
162
|
+
onAddProperty,
|
|
163
|
+
onRemoveProperty,
|
|
164
|
+
onEditProperty,
|
|
165
|
+
};
|
|
166
|
+
}
|