Explorar el Código

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

Yiwei Mao hace 7 meses
padre
commit
dd727c8d63

+ 4 - 0
apps/demo-fixed-layout/src/nodes/default-form-meta.tsx

@@ -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,
+  },
 };

+ 4 - 0
apps/demo-free-layout/src/nodes/default-form-meta.tsx

@@ -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,
+  },
 };

+ 7 - 0
common/config/rush/pnpm-lock.yaml

@@ -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==}
 

+ 2 - 1
packages/materials/form-materials/package.json

@@ -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:*",

+ 22 - 0
packages/materials/form-materials/src/components/json-schema-editor/components/blur-input.tsx

@@ -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)}
+    />
+  );
+}

+ 4 - 3
packages/materials/form-materials/src/components/json-schema-editor/index.tsx

@@ -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)}

+ 1 - 1
packages/materials/form-materials/src/components/variable-selector/index.tsx

@@ -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...'}
       />

+ 5 - 0
packages/materials/form-materials/src/effects/auto-rename-ref/config.json

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

+ 104 - 0
packages/materials/form-materials/src/effects/auto-rename-ref/index.ts

@@ -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;
+}

+ 1 - 0
packages/materials/form-materials/src/effects/index.ts

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

+ 9 - 2
packages/variable-engine/variable-core/src/ast/ast-registers.ts

@@ -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);