feat(material): auto rename effect (#261)

This commit is contained in:
Yiwei Mao 2025-05-23 12:41:45 +08:00 committed by GitHub
parent e9218afcaa
commit dd727c8d63
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 163 additions and 7 deletions

View File

@ -1,3 +1,4 @@
import { autoRenameRefEffect } from '@flowgram.ai/form-materials';
import { FormRenderProps, FormMeta, ValidateTrigger } from '@flowgram.ai/fixed-layout-editor';
import { FlowNodeJSON } from '../typings';
@ -27,4 +28,7 @@ export const defaultFormMeta: FormMeta<FlowNodeJSON['data']> = {
return undefined;
},
},
effect: {
inputsValues: autoRenameRefEffect,
},
};

View File

@ -1,4 +1,5 @@
import { FormRenderProps, FormMeta, ValidateTrigger } from '@flowgram.ai/free-layout-editor';
import { autoRenameRefEffect } from '@flowgram.ai/form-materials';
import { FlowNodeJSON } from '../typings';
import { FormHeader, FormContent, FormInputs, FormOutputs } from '../form-components';
@ -27,4 +28,7 @@ export const defaultFormMeta: FormMeta<FlowNodeJSON> = {
return undefined;
},
},
effect: {
inputsValues: autoRenameRefEffect,
},
};

View File

@ -1916,6 +1916,9 @@ importers:
commander:
specifier: ^11.0.0
version: 11.1.0
immer:
specifier: ~10.1.1
version: 10.1.1
inquirer:
specifier: ^9.2.7
version: 9.3.7
@ -11746,6 +11749,10 @@ packages:
dev: true
optional: true
/immer@10.1.1:
resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==}
dev: false
/immutable@5.0.3:
resolution: {integrity: sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==}

View File

@ -40,7 +40,8 @@
"nanoid": "^4.0.2",
"commander": "^11.0.0",
"chalk": "^5.3.0",
"inquirer": "^9.2.7"
"inquirer": "^9.2.7",
"immer": "~10.1.1"
},
"devDependencies": {
"@flowgram.ai/eslint-config": "workspace:*",

View File

@ -0,0 +1,22 @@
import React, { useEffect, useState } from 'react';
import Input, { InputProps } from '@douyinfe/semi-ui/lib/es/input';
export function BlurInput(props: InputProps) {
const [value, setValue] = useState('');
useEffect(() => {
setValue(props.value as string);
}, [props.value]);
return (
<Input
{...props}
value={value}
onChange={(value) => {
setValue(value);
}}
onBlur={(e) => props.onChange?.(value, e)}
/>
);
}

View File

@ -1,6 +1,6 @@
import React, { useMemo, useState } from 'react';
import { Button, Checkbox, IconButton, Input } from '@douyinfe/semi-ui';
import { Button, Checkbox, IconButton } from '@douyinfe/semi-ui';
import {
IconExpand,
IconShrink,
@ -31,6 +31,7 @@ import {
import { UIName } from './styles';
import { UIRow } from './styles';
import { usePropertiesEdit } from './hooks';
import { BlurInput } from './components/blur-input';
export function JsonSchemaEditor(props: {
value?: IJsonSchema;
@ -109,7 +110,7 @@ function PropertyEdit(props: {
<UIPropertyMain $expand={expand}>
<UIRow>
<UIName>
<Input
<BlurInput
placeholder={config?.placeholder ?? 'Input Variable Name'}
size="small"
value={name}
@ -162,7 +163,7 @@ function PropertyEdit(props: {
{expand && (
<UIExpandDetail>
<UILabel>{config?.descTitle ?? 'Description'}</UILabel>
<Input
<BlurInput
size="small"
value={description}
onChange={(value) => onChange('description', value)}

View File

@ -100,7 +100,7 @@ export const VariableSelector = ({
);
}}
showClear={false}
arrowIcon={value ? null : <IconChevronDownStroked size="small" />}
arrowIcon={<IconChevronDownStroked size="small" />}
triggerRender={triggerRender}
placeholder={config?.placeholder ?? 'Select Variable...'}
/>

View File

@ -0,0 +1,5 @@
{
"name": "auto-rename-ref",
"depMaterials": ["flow-value"],
"depPackages": ["lodash"]
}

View File

@ -0,0 +1,104 @@
import { isArray, isObject } from 'lodash';
import {
DataEvent,
Effect,
EffectOptions,
VariableFieldKeyRenameService,
} from '@flowgram.ai/editor';
import { IFlowRefValue } from '../../typings';
/**
* Auto rename ref when form item's key is renamed
*
* Example:
*
* formMeta: {
* effects: {
* "inputsValues": autoRenameRefEffect,
* }
* }
*/
export const autoRenameRefEffect: EffectOptions[] = [
{
event: DataEvent.onValueInit,
effect: ((params) => {
const { context, form, name } = params;
const renameService = context.node.getService(VariableFieldKeyRenameService);
const disposable = renameService.onRename(({ before, after }) => {
const beforeKeyPath = [
...before.parentFields.map((_field) => _field.key).reverse(),
before.key,
];
const afterKeyPath = [
...after.parentFields.map((_field) => _field.key).reverse(),
after.key,
];
// traverse rename refs inside form item 'name'
traverseRef(name, form.getValueIn(name), (_drilldownName, _v) => {
if (isRefMatch(_v, beforeKeyPath)) {
_v.content = [...afterKeyPath, ...(_v.content || [])?.slice(beforeKeyPath.length)];
form.setValueIn(_drilldownName, _v);
}
});
});
return () => {
disposable.dispose();
};
}) as Effect,
},
];
/**
* If ref value's keyPath is the under as targetKeyPath
* @param value
* @param targetKeyPath
* @returns
*/
function isRefMatch(value: IFlowRefValue, targetKeyPath: string[]) {
return targetKeyPath.every((_key, index) => _key === value.content?.[index]);
}
/**
* If value is ref
* @param value
* @returns
*/
function isRef(value: any): value is IFlowRefValue {
return (
value?.type === 'ref' && Array.isArray(value?.content) && typeof value?.content[0] === 'string'
);
}
/**
* Traverse value to find ref
* @param value
* @param options
* @returns
*/
function traverseRef(name: string, value: any, cb: (name: string, _v: IFlowRefValue) => void) {
if (isObject(value)) {
if (isRef(value)) {
cb(name, value);
return;
}
Object.entries(value).forEach(([_key, _value]) => {
traverseRef(`${name}.${_key}`, _value, cb);
});
return;
}
if (isArray(value)) {
value.forEach((_value, idx) => {
traverseRef(`${name}[${idx}]`, _value, cb);
});
return;
}
return;
}

View File

@ -1,2 +1,3 @@
export * from './provide-batch-input';
export * from './provide-batch-outputs';
export * from './auto-rename-ref';

View File

@ -13,7 +13,12 @@ import {
ObjectType,
StringType,
} from './type';
import { EnumerateExpression, KeyPathExpression, WrapArrayExpression } from './expression';
import {
EnumerateExpression,
// KeyPathExpression,
KeyPathExpressionV2,
WrapArrayExpression,
} from './expression';
import { Property, VariableDeclaration, VariableDeclarationList } from './declaration';
import { DataNode, MapNode } from './common';
import { ASTNode, ASTNodeRegistry } from './ast-node';
@ -41,7 +46,9 @@ export class ASTRegisters {
this.registerAST(Property);
this.registerAST(VariableDeclaration);
this.registerAST(VariableDeclarationList);
this.registerAST(KeyPathExpression);
// this.registerAST(KeyPathExpression);
this.registerAST(KeyPathExpressionV2);
this.registerAST(EnumerateExpression);
this.registerAST(WrapArrayExpression);
this.registerAST(MapNode);