@flowgram.ai/form-materials 0.1.0-alpha.8
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 +94 -0
- package/bin/materials.ts +137 -0
- package/bin/project.ts +86 -0
- package/dist/esm/index.js +1978 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/index.d.mts +285 -0
- package/dist/index.d.ts +285 -0
- package/dist/index.js +2018 -0
- package/dist/index.js.map +1 -0
- package/package.json +72 -0
- package/src/components/batch-variable-selector/config.json +5 -0
- package/src/components/batch-variable-selector/index.tsx +19 -0
- package/src/components/condition-row/config.json +5 -0
- package/src/components/condition-row/constants.ts +123 -0
- package/src/components/condition-row/hooks/useOp.tsx +45 -0
- package/src/components/condition-row/hooks/useRule.ts +26 -0
- package/src/components/condition-row/index.tsx +71 -0
- package/src/components/condition-row/styles.tsx +25 -0
- package/src/components/condition-row/types.ts +37 -0
- package/src/components/constant-input/config.json +6 -0
- package/src/components/constant-input/index.tsx +81 -0
- package/src/components/constant-input/types.ts +18 -0
- package/src/components/dynamic-value-input/config.json +5 -0
- package/src/components/dynamic-value-input/index.tsx +85 -0
- package/src/components/dynamic-value-input/styles.tsx +19 -0
- package/src/components/index.ts +7 -0
- package/src/components/json-schema-editor/components/blur-input.tsx +22 -0
- package/src/components/json-schema-editor/config.json +5 -0
- package/src/components/json-schema-editor/default-value.tsx +130 -0
- package/src/components/json-schema-editor/hooks.tsx +161 -0
- package/src/components/json-schema-editor/index.tsx +256 -0
- package/src/components/json-schema-editor/styles.tsx +235 -0
- package/src/components/json-schema-editor/types.ts +21 -0
- package/src/components/json-schema-editor/utils.ts +24 -0
- package/src/components/type-selector/config.json +5 -0
- package/src/components/type-selector/constants.tsx +359 -0
- package/src/components/type-selector/index.tsx +57 -0
- package/src/components/variable-selector/config.json +5 -0
- package/src/components/variable-selector/index.tsx +109 -0
- package/src/components/variable-selector/styles.tsx +43 -0
- package/src/components/variable-selector/use-variable-tree.tsx +101 -0
- package/src/effects/auto-rename-ref/config.json +5 -0
- package/src/effects/auto-rename-ref/index.ts +104 -0
- package/src/effects/index.ts +3 -0
- package/src/effects/provide-batch-input/config.json +5 -0
- package/src/effects/provide-batch-input/index.ts +38 -0
- package/src/effects/provide-batch-outputs/config.json +5 -0
- package/src/effects/provide-batch-outputs/index.ts +34 -0
- package/src/index.ts +4 -0
- package/src/typings/flow-value/config.json +5 -0
- package/src/typings/flow-value/index.ts +27 -0
- package/src/typings/index.ts +2 -0
- package/src/typings/json-schema/config.json +5 -0
- package/src/typings/json-schema/index.ts +31 -0
- package/src/utils/format-legacy-refs/config.json +5 -0
- package/src/utils/format-legacy-refs/index.ts +153 -0
- package/src/utils/format-legacy-refs/readme.md +38 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/json-schema/config.json +5 -0
- package/src/utils/json-schema/index.ts +161 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Input } from '@douyinfe/semi-ui';
|
|
4
|
+
|
|
5
|
+
import { ConditionRowValueType, Op } from './types';
|
|
6
|
+
import { UIContainer, UILeft, UIOperator, UIRight, UIValues } from './styles';
|
|
7
|
+
import { useRule } from './hooks/useRule';
|
|
8
|
+
import { useOp } from './hooks/useOp';
|
|
9
|
+
import { VariableSelector } from '../variable-selector';
|
|
10
|
+
import { DynamicValueInput } from '../dynamic-value-input';
|
|
11
|
+
import { JsonSchemaBasicType } from '../../typings';
|
|
12
|
+
|
|
13
|
+
interface PropTypes {
|
|
14
|
+
value?: ConditionRowValueType;
|
|
15
|
+
onChange: (value?: ConditionRowValueType) => void;
|
|
16
|
+
style?: React.CSSProperties;
|
|
17
|
+
readonly?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function ConditionRow({ style, value, onChange, readonly }: PropTypes) {
|
|
21
|
+
const { left, operator, right } = value || {};
|
|
22
|
+
const { rule } = useRule(left);
|
|
23
|
+
const { renderOpSelect, opConfig } = useOp({
|
|
24
|
+
rule,
|
|
25
|
+
op: operator,
|
|
26
|
+
onChange: (v) => onChange({ ...value, operator: v }),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const targetSchema = useMemo(() => {
|
|
30
|
+
const targetType: JsonSchemaBasicType | null = rule?.[operator as Op] || null;
|
|
31
|
+
return targetType ? { type: targetType, extra: { weak: true } } : null;
|
|
32
|
+
}, [rule, opConfig]);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<UIContainer style={style}>
|
|
36
|
+
<UIOperator>{renderOpSelect()}</UIOperator>
|
|
37
|
+
<UIValues>
|
|
38
|
+
<UILeft>
|
|
39
|
+
<VariableSelector
|
|
40
|
+
readonly={readonly}
|
|
41
|
+
style={{ width: '100%' }}
|
|
42
|
+
value={left?.content}
|
|
43
|
+
onChange={(v) =>
|
|
44
|
+
onChange({
|
|
45
|
+
...value,
|
|
46
|
+
left: {
|
|
47
|
+
type: 'ref',
|
|
48
|
+
content: v,
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
/>
|
|
53
|
+
</UILeft>
|
|
54
|
+
<UIRight>
|
|
55
|
+
{targetSchema ? (
|
|
56
|
+
<DynamicValueInput
|
|
57
|
+
readonly={readonly || !rule}
|
|
58
|
+
value={right}
|
|
59
|
+
schema={targetSchema}
|
|
60
|
+
onChange={(v) => onChange({ ...value, right: v })}
|
|
61
|
+
/>
|
|
62
|
+
) : (
|
|
63
|
+
<Input size="small" disabled value={opConfig?.rightDisplay || 'Empty'} />
|
|
64
|
+
)}
|
|
65
|
+
</UIRight>
|
|
66
|
+
</UIValues>
|
|
67
|
+
</UIContainer>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export { ConditionRowValueType };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
|
|
3
|
+
export const UIContainer = styled.div`
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
gap: 4px;
|
|
7
|
+
`;
|
|
8
|
+
|
|
9
|
+
export const UIOperator = styled.div``;
|
|
10
|
+
|
|
11
|
+
export const UILeft = styled.div`
|
|
12
|
+
width: 100%;
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
export const UIRight = styled.div`
|
|
16
|
+
width: 100%;
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
export const UIValues = styled.div`
|
|
20
|
+
flex-grow: 1;
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
align-items: center;
|
|
24
|
+
gap: 4px;
|
|
25
|
+
`;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { IFlowConstantRefValue, IFlowRefValue, JsonSchemaBasicType } from '../../typings';
|
|
2
|
+
|
|
3
|
+
export enum Op {
|
|
4
|
+
EQ = 'eq',
|
|
5
|
+
NEQ = 'neq',
|
|
6
|
+
GT = 'gt',
|
|
7
|
+
GTE = 'gte',
|
|
8
|
+
LT = 'lt',
|
|
9
|
+
LTE = 'lte',
|
|
10
|
+
IN = 'in',
|
|
11
|
+
NIN = 'nin',
|
|
12
|
+
CONTAINS = 'contains',
|
|
13
|
+
NOT_CONTAINS = 'not_contains',
|
|
14
|
+
IS_EMPTY = 'is_empty',
|
|
15
|
+
IS_NOT_EMPTY = 'is_not_empty',
|
|
16
|
+
IS_TRUE = 'is_true',
|
|
17
|
+
IS_FALSE = 'is_false',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface OpConfig {
|
|
21
|
+
label: string;
|
|
22
|
+
abbreviation: string;
|
|
23
|
+
// When right is not a value, display this text
|
|
24
|
+
rightDisplay?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type OpConfigs = Record<Op, OpConfig>;
|
|
28
|
+
|
|
29
|
+
export type IRule = Partial<Record<Op, JsonSchemaBasicType | null>>;
|
|
30
|
+
|
|
31
|
+
export type IRules = Record<JsonSchemaBasicType, IRule>;
|
|
32
|
+
|
|
33
|
+
export interface ConditionRowValueType {
|
|
34
|
+
left?: IFlowRefValue;
|
|
35
|
+
operator?: Op;
|
|
36
|
+
right?: IFlowConstantRefValue;
|
|
37
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/* eslint-disable react/prop-types */
|
|
2
|
+
import React, { useMemo } from 'react';
|
|
3
|
+
|
|
4
|
+
import { Input, InputNumber, Select } from '@douyinfe/semi-ui';
|
|
5
|
+
|
|
6
|
+
import { PropsType, Strategy } from './types';
|
|
7
|
+
|
|
8
|
+
const defaultStrategies: Strategy[] = [
|
|
9
|
+
{
|
|
10
|
+
hit: (schema) => schema?.type === 'string',
|
|
11
|
+
Renderer: (props) => (
|
|
12
|
+
<Input placeholder="Please Input String" size="small" disabled={props.readonly} {...props} />
|
|
13
|
+
),
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
hit: (schema) => schema?.type === 'number',
|
|
17
|
+
Renderer: (props) => (
|
|
18
|
+
<InputNumber
|
|
19
|
+
placeholder="Please Input Number"
|
|
20
|
+
size="small"
|
|
21
|
+
disabled={props.readonly}
|
|
22
|
+
hideButtons
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
hit: (schema) => schema?.type === 'integer',
|
|
29
|
+
Renderer: (props) => (
|
|
30
|
+
<InputNumber
|
|
31
|
+
placeholder="Please Input Integer"
|
|
32
|
+
size="small"
|
|
33
|
+
disabled={props.readonly}
|
|
34
|
+
hideButtons
|
|
35
|
+
precision={0}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
),
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
hit: (schema) => schema?.type === 'boolean',
|
|
42
|
+
Renderer: (props) => {
|
|
43
|
+
const { value, onChange, ...rest } = props;
|
|
44
|
+
return (
|
|
45
|
+
<Select
|
|
46
|
+
placeholder="Please Select Boolean"
|
|
47
|
+
size="small"
|
|
48
|
+
disabled={props.readonly}
|
|
49
|
+
optionList={[
|
|
50
|
+
{ label: 'True', value: 1 },
|
|
51
|
+
{ label: 'False', value: 0 },
|
|
52
|
+
]}
|
|
53
|
+
value={value ? 1 : 0}
|
|
54
|
+
onChange={(value) => onChange?.(!!value)}
|
|
55
|
+
{...rest}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
export function ConstantInput(props: PropsType) {
|
|
63
|
+
const { value, onChange, schema, strategies: extraStrategies, readonly, ...rest } = props;
|
|
64
|
+
|
|
65
|
+
const strategies = useMemo(
|
|
66
|
+
() => [...defaultStrategies, ...(extraStrategies || [])],
|
|
67
|
+
[extraStrategies]
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const Renderer = useMemo(() => {
|
|
71
|
+
const strategy = strategies.find((_strategy) => _strategy.hit(schema));
|
|
72
|
+
|
|
73
|
+
return strategy?.Renderer;
|
|
74
|
+
}, [strategies, schema]);
|
|
75
|
+
|
|
76
|
+
if (!Renderer) {
|
|
77
|
+
return <Input size="small" disabled placeholder="Unsupported type" />;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return <Renderer value={value} onChange={onChange} readonly={readonly} {...rest} />;
|
|
81
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { IJsonSchema } from '../../typings';
|
|
2
|
+
|
|
3
|
+
export interface Strategy<Value = any> {
|
|
4
|
+
hit: (schema: IJsonSchema) => boolean;
|
|
5
|
+
Renderer: React.FC<RendererProps<Value>>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface RendererProps<Value = any> {
|
|
9
|
+
value?: Value;
|
|
10
|
+
onChange?: (value: Value) => void;
|
|
11
|
+
readonly?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface PropsType extends RendererProps {
|
|
15
|
+
schema: IJsonSchema;
|
|
16
|
+
strategies?: Strategy[];
|
|
17
|
+
[key: string]: any;
|
|
18
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import { IconButton } from '@douyinfe/semi-ui';
|
|
4
|
+
import { IconSetting } from '@douyinfe/semi-icons';
|
|
5
|
+
|
|
6
|
+
import { Strategy } from '../constant-input/types';
|
|
7
|
+
import { ConstantInput } from '../constant-input';
|
|
8
|
+
import { IFlowConstantRefValue } from '../../typings/flow-value';
|
|
9
|
+
import { UIContainer, UIMain, UITrigger } from './styles';
|
|
10
|
+
import { VariableSelector } from '../variable-selector';
|
|
11
|
+
import { IJsonSchema } from '../../typings';
|
|
12
|
+
|
|
13
|
+
interface PropsType {
|
|
14
|
+
value?: IFlowConstantRefValue;
|
|
15
|
+
onChange: (value?: IFlowConstantRefValue) => void;
|
|
16
|
+
readonly?: boolean;
|
|
17
|
+
hasError?: boolean;
|
|
18
|
+
style?: React.CSSProperties;
|
|
19
|
+
schema?: IJsonSchema;
|
|
20
|
+
constantProps?: {
|
|
21
|
+
strategies?: Strategy[];
|
|
22
|
+
[key: string]: any;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function DynamicValueInput({
|
|
27
|
+
value,
|
|
28
|
+
onChange,
|
|
29
|
+
readonly,
|
|
30
|
+
style,
|
|
31
|
+
schema,
|
|
32
|
+
constantProps,
|
|
33
|
+
}: PropsType) {
|
|
34
|
+
// When is number type, include integer as well
|
|
35
|
+
const includeSchema = useMemo(() => {
|
|
36
|
+
if (schema?.type === 'number') {
|
|
37
|
+
return [schema, { type: 'integer' }];
|
|
38
|
+
}
|
|
39
|
+
return schema;
|
|
40
|
+
}, [schema]);
|
|
41
|
+
|
|
42
|
+
const renderMain = () => {
|
|
43
|
+
if (value?.type === 'ref') {
|
|
44
|
+
// Display Variable Or Delete
|
|
45
|
+
return (
|
|
46
|
+
<VariableSelector
|
|
47
|
+
value={value?.content}
|
|
48
|
+
onChange={(_v) => onChange(_v ? { type: 'ref', content: _v } : undefined)}
|
|
49
|
+
includeSchema={includeSchema}
|
|
50
|
+
readonly={readonly}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<ConstantInput
|
|
57
|
+
value={value?.content}
|
|
58
|
+
onChange={(_v) => onChange({ type: 'constant', content: _v })}
|
|
59
|
+
schema={schema || { type: 'string' }}
|
|
60
|
+
readonly={readonly}
|
|
61
|
+
{...constantProps}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const renderTrigger = () => (
|
|
67
|
+
<VariableSelector
|
|
68
|
+
style={{ width: '100%' }}
|
|
69
|
+
value={value?.type === 'ref' ? value?.content : undefined}
|
|
70
|
+
onChange={(_v) => onChange({ type: 'ref', content: _v })}
|
|
71
|
+
includeSchema={includeSchema}
|
|
72
|
+
readonly={readonly}
|
|
73
|
+
triggerRender={() => (
|
|
74
|
+
<IconButton disabled={readonly} size="small" icon={<IconSetting size="small" />} />
|
|
75
|
+
)}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<UIContainer style={style}>
|
|
81
|
+
<UIMain>{renderMain()}</UIMain>
|
|
82
|
+
<UITrigger>{renderTrigger()}</UITrigger>
|
|
83
|
+
</UIContainer>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
|
|
3
|
+
export const UIContainer = styled.div`
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
gap: 5px;
|
|
7
|
+
`;
|
|
8
|
+
|
|
9
|
+
export const UIMain = styled.div`
|
|
10
|
+
flex-grow: 1;
|
|
11
|
+
|
|
12
|
+
& .semi-tree-select,
|
|
13
|
+
& .semi-input-number,
|
|
14
|
+
& .semi-select {
|
|
15
|
+
width: 100%;
|
|
16
|
+
}
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
export const UITrigger = styled.div``;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './variable-selector';
|
|
2
|
+
export * from './type-selector';
|
|
3
|
+
export * from './json-schema-editor';
|
|
4
|
+
export * from './batch-variable-selector';
|
|
5
|
+
export * from './constant-input';
|
|
6
|
+
export * from './dynamic-value-input';
|
|
7
|
+
export * from './condition-row';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import Input, { InputProps } from '@douyinfe/semi-ui/lib/es/input';
|
|
4
|
+
|
|
5
|
+
export function BlurInput(props: InputProps) {
|
|
6
|
+
const [value, setValue] = useState('');
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
setValue(props.value as string);
|
|
10
|
+
}, [props.value]);
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<Input
|
|
14
|
+
{...props}
|
|
15
|
+
value={value}
|
|
16
|
+
onChange={(value) => {
|
|
17
|
+
setValue(value);
|
|
18
|
+
}}
|
|
19
|
+
onBlur={(e) => props.onChange?.(value, e)}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import React, { useRef, useState, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
import { IconButton, JsonViewer, Tooltip } from '@douyinfe/semi-ui';
|
|
4
|
+
import { IconBrackets } from '@douyinfe/semi-icons';
|
|
5
|
+
|
|
6
|
+
import { getValueType } from './utils';
|
|
7
|
+
import {
|
|
8
|
+
ConstantInputWrapper,
|
|
9
|
+
JSONHeader,
|
|
10
|
+
JSONHeaderLeft,
|
|
11
|
+
JSONHeaderRight,
|
|
12
|
+
JSONViewerWrapper,
|
|
13
|
+
} from './styles';
|
|
14
|
+
import { ConstantInput } from '../constant-input';
|
|
15
|
+
import { IJsonSchema } from '../../typings';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 根据不同的数据类型渲染对应的默认值输入组件。
|
|
19
|
+
* @param props - 组件属性,包括 value, type, placeholder, onChange。
|
|
20
|
+
* @returns 返回对应类型的输入组件或 null。
|
|
21
|
+
*/
|
|
22
|
+
export function DefaultValue(props: {
|
|
23
|
+
value: any;
|
|
24
|
+
schema?: IJsonSchema;
|
|
25
|
+
name?: string;
|
|
26
|
+
type?: string;
|
|
27
|
+
placeholder?: string;
|
|
28
|
+
jsonFormatText?: string;
|
|
29
|
+
onChange: (value: any) => void;
|
|
30
|
+
}) {
|
|
31
|
+
const { value, schema, type, onChange, placeholder, jsonFormatText } = props;
|
|
32
|
+
|
|
33
|
+
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
34
|
+
const JsonViewerRef = useRef<JsonViewer>(null);
|
|
35
|
+
|
|
36
|
+
// 为 JsonViewer 添加状态管理
|
|
37
|
+
const [internalJsonValue, setInternalJsonValue] = useState<string>(
|
|
38
|
+
getValueType(value) === 'string' ? value : ''
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// 使用 useCallback 创建稳定的回调函数
|
|
42
|
+
const handleJsonChange = useCallback((val: string) => {
|
|
43
|
+
// 只在值真正改变时才更新状态
|
|
44
|
+
if (val !== internalJsonValue) {
|
|
45
|
+
setInternalJsonValue(val);
|
|
46
|
+
}
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
// 处理编辑完成事件
|
|
50
|
+
const handleEditComplete = useCallback(() => {
|
|
51
|
+
// 只有当存在key,编辑完成时才触发父组件的 onChange
|
|
52
|
+
onChange(internalJsonValue);
|
|
53
|
+
// 确保在更新后移除焦点
|
|
54
|
+
requestAnimationFrame(() => {
|
|
55
|
+
// JsonViewerRef.current?.format();
|
|
56
|
+
wrapperRef.current?.blur();
|
|
57
|
+
});
|
|
58
|
+
setJsonReadOnly(true);
|
|
59
|
+
}, [internalJsonValue, onChange]);
|
|
60
|
+
|
|
61
|
+
const [jsonReadOnly, setJsonReadOnly] = useState<boolean>(true);
|
|
62
|
+
|
|
63
|
+
const handleFormatJson = useCallback(() => {
|
|
64
|
+
try {
|
|
65
|
+
const parsed = JSON.parse(internalJsonValue);
|
|
66
|
+
const formatted = JSON.stringify(parsed, null, 4);
|
|
67
|
+
setInternalJsonValue(formatted);
|
|
68
|
+
onChange(formatted);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error('Invalid JSON:', error);
|
|
71
|
+
}
|
|
72
|
+
}, [internalJsonValue, onChange]);
|
|
73
|
+
|
|
74
|
+
return type === 'object' ? (
|
|
75
|
+
<>
|
|
76
|
+
<JSONHeader>
|
|
77
|
+
<JSONHeaderLeft>json</JSONHeaderLeft>
|
|
78
|
+
<JSONHeaderRight>
|
|
79
|
+
<Tooltip content={jsonFormatText ?? 'Format'}>
|
|
80
|
+
<IconButton
|
|
81
|
+
icon={<IconBrackets style={{ color: 'var(--semi-color-primary)' }} />}
|
|
82
|
+
size="small"
|
|
83
|
+
type="tertiary"
|
|
84
|
+
theme="borderless"
|
|
85
|
+
onClick={handleFormatJson}
|
|
86
|
+
/>
|
|
87
|
+
</Tooltip>
|
|
88
|
+
</JSONHeaderRight>
|
|
89
|
+
</JSONHeader>
|
|
90
|
+
|
|
91
|
+
<JSONViewerWrapper
|
|
92
|
+
ref={wrapperRef}
|
|
93
|
+
tabIndex={-1}
|
|
94
|
+
onBlur={(e) => {
|
|
95
|
+
if (wrapperRef.current && !wrapperRef.current?.contains(e.relatedTarget as Node)) {
|
|
96
|
+
handleEditComplete();
|
|
97
|
+
}
|
|
98
|
+
}}
|
|
99
|
+
onClick={(e: React.MouseEvent) => {
|
|
100
|
+
setJsonReadOnly(false);
|
|
101
|
+
}}
|
|
102
|
+
>
|
|
103
|
+
<JsonViewer
|
|
104
|
+
ref={JsonViewerRef}
|
|
105
|
+
value={getValueType(value) === 'string' ? value : ''}
|
|
106
|
+
height={120}
|
|
107
|
+
width="100%"
|
|
108
|
+
showSearch={false}
|
|
109
|
+
options={{
|
|
110
|
+
readOnly: jsonReadOnly,
|
|
111
|
+
formatOptions: { tabSize: 4, insertSpaces: true, eol: '\n' },
|
|
112
|
+
}}
|
|
113
|
+
style={{
|
|
114
|
+
padding: 0,
|
|
115
|
+
}}
|
|
116
|
+
onChange={handleJsonChange}
|
|
117
|
+
/>
|
|
118
|
+
</JSONViewerWrapper>
|
|
119
|
+
</>
|
|
120
|
+
) : (
|
|
121
|
+
<ConstantInputWrapper>
|
|
122
|
+
<ConstantInput
|
|
123
|
+
value={value}
|
|
124
|
+
onChange={(_v) => onChange(_v)}
|
|
125
|
+
schema={schema || { type: 'string' }}
|
|
126
|
+
placeholder={placeholder ?? 'Default value if parameter is not provided'}
|
|
127
|
+
/>
|
|
128
|
+
</ConstantInputWrapper>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { IJsonSchema } from '../../typings';
|
|
4
|
+
import { PropertyValueType } from './types';
|
|
5
|
+
|
|
6
|
+
let _id = 0;
|
|
7
|
+
function genId() {
|
|
8
|
+
return _id++;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function getDrilldownSchema(
|
|
12
|
+
value?: PropertyValueType,
|
|
13
|
+
path?: (keyof PropertyValueType)[]
|
|
14
|
+
): { schema?: PropertyValueType | null; path?: (keyof PropertyValueType)[] } {
|
|
15
|
+
if (!value) {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (value.type === 'array' && value.items) {
|
|
20
|
+
return getDrilldownSchema(value.items, [...(path || []), 'items']);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return { schema: value, path };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function usePropertiesEdit(
|
|
27
|
+
value?: PropertyValueType,
|
|
28
|
+
onChange?: (value: PropertyValueType) => void
|
|
29
|
+
) {
|
|
30
|
+
// Get drilldown (array.items.items...)
|
|
31
|
+
const drilldown = useMemo(() => getDrilldownSchema(value), [value, value?.type, value?.items]);
|
|
32
|
+
|
|
33
|
+
const isDrilldownObject = drilldown.schema?.type === 'object';
|
|
34
|
+
|
|
35
|
+
// Generate Init Property List
|
|
36
|
+
const initPropertyList = useMemo(
|
|
37
|
+
() =>
|
|
38
|
+
isDrilldownObject
|
|
39
|
+
? Object.entries(drilldown.schema?.properties || {})
|
|
40
|
+
.sort(([, a], [, b]) => (a.extra?.index ?? 0) - (b.extra?.index ?? 0))
|
|
41
|
+
.map(
|
|
42
|
+
([name, _value], index) =>
|
|
43
|
+
({
|
|
44
|
+
key: genId(),
|
|
45
|
+
name,
|
|
46
|
+
isPropertyRequired: drilldown.schema?.required?.includes(name) || false,
|
|
47
|
+
..._value,
|
|
48
|
+
extra: {
|
|
49
|
+
...(_value.extra || {}),
|
|
50
|
+
index,
|
|
51
|
+
},
|
|
52
|
+
} as PropertyValueType)
|
|
53
|
+
)
|
|
54
|
+
: [],
|
|
55
|
+
[isDrilldownObject]
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const [propertyList, setPropertyList] = useState<PropertyValueType[]>(initPropertyList);
|
|
59
|
+
|
|
60
|
+
const mountRef = useRef(false);
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
// If initRef is true, it means the component has been mounted
|
|
64
|
+
if (mountRef.current) {
|
|
65
|
+
// If the value is changed, update the property list
|
|
66
|
+
setPropertyList((_list) => {
|
|
67
|
+
const nameMap = new Map<string, PropertyValueType>();
|
|
68
|
+
|
|
69
|
+
for (const _property of _list) {
|
|
70
|
+
if (_property.name) {
|
|
71
|
+
nameMap.set(_property.name, _property);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return Object.entries(drilldown.schema?.properties || {})
|
|
75
|
+
.sort(([, a], [, b]) => (a.extra?.index ?? 0) - (b.extra?.index ?? 0))
|
|
76
|
+
.map(([name, _value]) => {
|
|
77
|
+
const _property = nameMap.get(name);
|
|
78
|
+
if (_property) {
|
|
79
|
+
return {
|
|
80
|
+
key: _property.key,
|
|
81
|
+
name,
|
|
82
|
+
isPropertyRequired: drilldown.schema?.required?.includes(name) || false,
|
|
83
|
+
..._value,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
key: genId(),
|
|
88
|
+
name,
|
|
89
|
+
isPropertyRequired: drilldown.schema?.required?.includes(name) || false,
|
|
90
|
+
..._value,
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
mountRef.current = true;
|
|
96
|
+
}, [drilldown.schema]);
|
|
97
|
+
|
|
98
|
+
const updatePropertyList = (updater: (list: PropertyValueType[]) => PropertyValueType[]) => {
|
|
99
|
+
setPropertyList((_list) => {
|
|
100
|
+
const next = updater(_list);
|
|
101
|
+
|
|
102
|
+
// onChange to parent
|
|
103
|
+
const nextProperties: Record<string, IJsonSchema> = {};
|
|
104
|
+
const nextRequired: string[] = [];
|
|
105
|
+
|
|
106
|
+
for (const _property of next) {
|
|
107
|
+
if (!_property.name) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
nextProperties[_property.name] = _property;
|
|
112
|
+
|
|
113
|
+
if (_property.isPropertyRequired) {
|
|
114
|
+
nextRequired.push(_property.name);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let drilldownSchema = value || {};
|
|
119
|
+
if (drilldown.path) {
|
|
120
|
+
drilldownSchema = drilldown.path.reduce((acc, key) => acc[key], value || {});
|
|
121
|
+
}
|
|
122
|
+
drilldownSchema.properties = nextProperties;
|
|
123
|
+
drilldownSchema.required = nextRequired;
|
|
124
|
+
|
|
125
|
+
onChange?.(value || {});
|
|
126
|
+
|
|
127
|
+
return next;
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const onAddProperty = () => {
|
|
132
|
+
updatePropertyList((_list) => [
|
|
133
|
+
..._list,
|
|
134
|
+
{ key: genId(), name: '', type: 'string', extra: { index: _list.length + 1 } },
|
|
135
|
+
]);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const onRemoveProperty = (key: number) => {
|
|
139
|
+
updatePropertyList((_list) => _list.filter((_property) => _property.key !== key));
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const onEditProperty = (key: number, nextValue: PropertyValueType) => {
|
|
143
|
+
updatePropertyList((_list) =>
|
|
144
|
+
_list.map((_property) => (_property.key === key ? nextValue : _property))
|
|
145
|
+
);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
if (!isDrilldownObject) {
|
|
150
|
+
setPropertyList([]);
|
|
151
|
+
}
|
|
152
|
+
}, [isDrilldownObject]);
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
propertyList,
|
|
156
|
+
isDrilldownObject,
|
|
157
|
+
onAddProperty,
|
|
158
|
+
onRemoveProperty,
|
|
159
|
+
onEditProperty,
|
|
160
|
+
};
|
|
161
|
+
}
|