Explorar el Código

feat(material): skip variable context + datetime support (#758)

* feat(material): skip variable context

* feat(json-schema): add format and date time
Yiwei Mao hace 4 meses
padre
commit
32620b7960

+ 8 - 0
packages/materials/form-materials/src/components/condition-row/constants.ts

@@ -60,6 +60,14 @@ export const defaultRules: IRules = {
     [Op.IS_EMPTY]: null,
     [Op.IS_NOT_EMPTY]: null,
   },
+  ['date-time']: {
+    [Op.EQ]: 'date-time',
+    [Op.NEQ]: 'date-time',
+    [Op.GT]: 'date-time',
+    [Op.GTE]: 'date-time',
+    [Op.LT]: 'date-time',
+    [Op.LTE]: 'date-time',
+  },
 };
 
 export const defaultOpConfigs: OpConfigs = {

+ 16 - 0
packages/materials/form-materials/src/components/variable-selector/context.tsx

@@ -0,0 +1,16 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { createContext, useContext } from 'react';
+
+import { BaseVariableField } from '@flowgram.ai/editor';
+
+export const VariableSelectorContext = createContext<{
+  skipVariable?: (variable?: BaseVariableField) => boolean;
+}>({});
+
+export const useVariableSelectorContext = () => useContext(VariableSelectorContext);
+
+export const VariableSelectorProvider = VariableSelectorContext.Provider;

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

@@ -16,6 +16,7 @@ import { createInjectMaterial } from '@/shared';
 
 import { useVariableTree } from './use-variable-tree';
 import { UIPopoverContent, UIRootTitle, UITag, UITreeSelect, UIVarName } from './styles';
+import { useVariableSelectorContext } from './context';
 
 export interface VariableSelectorProps {
   value?: string[];
@@ -45,7 +46,13 @@ export const VariableSelector = ({
   hasError,
   triggerRender,
 }: VariableSelectorProps) => {
-  const treeData = useVariableTree({ includeSchema, excludeSchema });
+  const { skipVariable } = useVariableSelectorContext();
+
+  const treeData = useVariableTree({
+    includeSchema,
+    excludeSchema,
+    skipVariable,
+  });
 
   const treeValue = useMemo(() => {
     if (typeof value === 'string') {
@@ -139,3 +146,5 @@ export const VariableSelector = ({
 
 VariableSelector.renderKey = 'variable-selector-render-key';
 export const InjectVariableSelector = createInjectMaterial(VariableSelector);
+
+export { VariableSelectorProvider } from './context';

+ 3 - 3
packages/materials/form-materials/src/components/variable-selector/use-variable-tree.tsx

@@ -21,9 +21,9 @@ type VariableField = BaseVariableField<{
 export function useVariableTree(params: {
   includeSchema?: IJsonSchema | IJsonSchema[];
   excludeSchema?: IJsonSchema | IJsonSchema[];
-  customSkip?: (variable: VariableField) => boolean;
+  skipVariable?: (variable: VariableField) => boolean;
 }): TreeNodeData[] {
-  const { includeSchema, excludeSchema, customSkip } = params;
+  const { includeSchema, excludeSchema, skipVariable } = params;
 
   const typeManager = useTypeManager();
   const variables = useAvailableVariables();
@@ -69,7 +69,7 @@ export function useVariableTree(params: {
     const isSchemaExclude = excludeSchema
       ? JsonSchemaUtils.isASTMatchSchema(type, excludeSchema)
       : false;
-    const isCustomSkip = customSkip ? customSkip(variable) : false;
+    const isCustomSkip = skipVariable ? skipVariable(variable) : false;
 
     // disabled in meta when created
     const isMetaDisabled = variable.meta?.disabled;

+ 25 - 0
packages/materials/form-materials/src/plugins/json-schema-preset/type-definition/date-time.tsx

@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+/* eslint-disable react/prop-types */
+import React from 'react';
+
+import { DatePicker } from '@douyinfe/semi-ui';
+
+import { type JsonSchemaTypeRegistry } from '../manager';
+
+export const dateTimeRegistry: Partial<JsonSchemaTypeRegistry> = {
+  type: 'date-time',
+  ConstantRenderer: (props) => (
+    <DatePicker
+      size="small"
+      type="dateTime"
+      density="compact"
+      style={{ width: '100%', ...(props.style || {}) }}
+      disabled={props.readonly}
+      {...props}
+    />
+  ),
+};

+ 2 - 0
packages/materials/form-materials/src/plugins/json-schema-preset/type-definition/index.tsx

@@ -9,6 +9,7 @@ import { stringRegistry } from './string';
 import { objectRegistry } from './object';
 import { numberRegistry } from './number';
 import { integerRegistry } from './integer';
+import { dateTimeRegistry } from './date-time';
 import { booleanRegistry } from './boolean';
 import { arrayRegistry } from './array';
 
@@ -19,6 +20,7 @@ export const jsonSchemaTypePreset = [
   integerRegistry,
   booleanRegistry,
   arrayRegistry,
+  dateTimeRegistry,
 ];
 
 jsonSchemaTypePreset.forEach((_type) => jsonSchemaTypeManager.register(_type));

+ 5 - 0
packages/variable-engine/json-schema/src/json-schema/json-schema-type-manager.tsx

@@ -8,6 +8,7 @@ import { injectable } from 'inversify';
 
 import { IJsonSchema, JsonSchemaTypeRegistry, JsonSchemaTypeRegistryCreator } from './types';
 import { defaultTypeDefinitionRegistry } from './type-definition/default';
+import { dateTimeRegistryCreator } from './type-definition/date-time';
 import { BaseTypeManager } from '../base';
 import {
   arrayRegistryCreator,
@@ -37,6 +38,9 @@ export class JsonSchemaTypeManager<
     if (typeSchema.enum) {
       return 'enum';
     }
+    if (typeSchema.format && typeSchema.type === 'string') {
+      return typeSchema.format;
+    }
 
     return typeSchema.type || typeSchema.$ref || 'unknown';
   }
@@ -54,6 +58,7 @@ export class JsonSchemaTypeManager<
       arrayRegistryCreator,
       unknownRegistryCreator,
       mapRegistryCreator,
+      dateTimeRegistryCreator,
     ];
 
     registries.forEach((registry) => {

+ 58 - 0
packages/variable-engine/json-schema/src/json-schema/type-definition/date-time.tsx

@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React from 'react';
+
+import { JsonSchemaTypeRegistryCreator } from '../types';
+
+export const dateTimeRegistryCreator: JsonSchemaTypeRegistryCreator = () => ({
+  type: 'date-time',
+
+  label: 'DateTime',
+
+  icon: (
+    <svg
+      viewBox="0 0 24 24"
+      fill="none"
+      xmlns="http://www.w3.org/2000/svg"
+      width="1em"
+      height="1em"
+      focusable="false"
+      aria-hidden="true"
+    >
+      <path
+        d="M2 5v14a3 3 0 0 0 3 3h7.1a7.02 7.02 0 0 1-1.43-2H6a2 2 0 0 1-2-2V8a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2.67c.75.36 1.43.85 2 1.43V5a3 3 0 0 0-3-3H5a3 3 0 0 0-3 3Z"
+        fill="currentColor"
+      ></path>
+      <path d="M16 10h1c-.54 0-1.06.06-1.57.18A1 1 0 0 1 16 10Z" fill="currentColor"></path>
+      <path
+        d="M13.5 10.94a1 1 0 0 0-1-.94h-1a1 1 0 0 0-1 1v1a1 1 0 0 0 .77.97 7.03 7.03 0 0 1 2.23-2.03Z"
+        fill="currentColor"
+      ></path>
+      <path
+        d="M7 10a1 1 0 0 0-1 1v1a1 1 0 0 0 1 1h1a1 1 0 0 0 1-1v-1a1 1 0 0 0-1-1H7Z"
+        fill="currentColor"
+      ></path>
+      <path
+        d="M6 16a1 1 0 0 1 1-1h1a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1v-1Z"
+        fill="currentColor"
+      ></path>
+      <path
+        fillRule="evenodd"
+        clipRule="evenodd"
+        d="M22 17a5 5 0 1 1-10 0 5 5 0 0 1 10 0Zm-4-2a1 1 0 1 0-2 0v2c0 .27.1.52.3.7l1.5 1.5a1 1 0 0 0 1.4-1.4L18 16.58V15Z"
+        fill="currentColor"
+      ></path>
+    </svg>
+  ),
+
+  getDefaultSchema: () => ({
+    type: 'date-time',
+  }),
+
+  getValueText: (value?: unknown) => (value ? `${value}` : ''),
+
+  getDefaultValue: () => '',
+});

+ 5 - 0
packages/variable-engine/json-schema/src/json-schema/types.ts

@@ -17,6 +17,11 @@ export type JsonSchemaBasicType =
 
 export interface IJsonSchema<T = string> {
   type?: T;
+  /**
+   * The format of string
+   * https://json-schema.org/understanding-json-schema/reference/type#format
+   */
+  format?: string;
   default?: any;
   title?: string;
   description?: string;

+ 2 - 1
packages/variable-engine/json-schema/src/json-schema/utils.ts

@@ -72,7 +72,7 @@ export namespace JsonSchemaUtils {
           valueType: schemaToAST(jsonSchema.additionalProperties!),
         });
       case 'string':
-        return ASTFactory.createString();
+        return ASTFactory.createString({ format: jsonSchema.format });
       case 'number':
         return ASTFactory.createNumber();
       case 'boolean':
@@ -114,6 +114,7 @@ export namespace JsonSchemaUtils {
     if (ASTMatch.isString(typeAST)) {
       return {
         type: 'string',
+        format: typeAST.format,
       };
     }
 

+ 5 - 1
packages/variable-engine/variable-core/src/ast/factory.ts

@@ -4,6 +4,7 @@
  */
 
 import { ASTKind, ASTNodeJSON } from './types';
+import { StringJSON } from './type/string';
 import { MapJSON } from './type/map';
 import { ArrayJSON } from './type/array';
 import { CustomTypeJSON, ObjectJSON, UnionJSON } from './type';
@@ -20,7 +21,10 @@ export namespace ASTFactory {
    * 类型相关
    * @returns
    */
-  export const createString = () => ({ kind: ASTKind.String });
+  export const createString = (json?: StringJSON) => ({
+    kind: ASTKind.String,
+    ...(json || {}),
+  });
   export const createNumber = () => ({ kind: ASTKind.Number });
   export const createBoolean = () => ({ kind: ASTKind.Boolean });
   export const createInteger = () => ({ kind: ASTKind.Integer });

+ 21 - 2
packages/variable-engine/variable-core/src/ast/type/string.ts

@@ -7,12 +7,31 @@ import { ASTKind } from '../types';
 import { ASTNodeFlags } from '../flags';
 import { BaseType } from './base-type';
 
+export interface StringJSON {
+  /**
+   * https://json-schema.org/understanding-json-schema/reference/type#format
+   */
+  format?: string;
+}
+
 export class StringType extends BaseType {
   public flags: ASTNodeFlags = ASTNodeFlags.BasicType;
 
   static kind: string = ASTKind.String;
 
-  fromJSON(): void {
-    // noop
+  protected _format?: string;
+
+  /**
+   * https://json-schema.org/understanding-json-schema/reference/type#format
+   */
+  get format() {
+    return this._format;
+  }
+
+  fromJSON(json?: StringJSON): void {
+    if (json?.format !== this._format) {
+      this._format = json?.format;
+      this.fireChange();
+    }
   }
 }