Quellcode durchsuchen

feat(material): db condition row (#741)

Yiwei Mao vor 4 Monaten
Ursprung
Commit
0c8f7eec76

+ 4 - 0
packages/materials/form-materials/src/components/condition-row/index.tsx

@@ -15,6 +15,7 @@ import { ConditionRowValueType, IRules, OpConfigs } from './types';
 import { UIContainer, UILeft, UIOperator, UIRight, UIValues } from './styles';
 import { useRule } from './hooks/useRule';
 import { useOp } from './hooks/useOp';
+import { defaultOpConfigs, defaultRules } from './constants';
 
 interface PropTypes {
   value?: ConditionRowValueType;
@@ -96,4 +97,7 @@ export function ConditionRow({
   );
 }
 
+ConditionRow.defaultRules = defaultRules;
+ConditionRow.defaultOpConfigs = defaultOpConfigs;
+
 export { type ConditionRowValueType };

+ 66 - 0
packages/materials/form-materials/src/components/db-condition-row/hooks/use-left.tsx

@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { useMemo } from 'react';
+import React from 'react';
+
+import { JsonSchemaTypeManager, useTypeManager } from '@flowgram.ai/json-schema';
+import { Icon } from '@douyinfe/semi-ui';
+
+import { ConditionRow } from '@/components';
+
+import { DBConditionOptionType, IRules } from '../types';
+import { UIOptionLabel, UISelect } from '../styles';
+
+const defaultRules = ConditionRow.defaultRules;
+
+interface HookParams {
+  left?: string;
+  options?: DBConditionOptionType[];
+  userRules?: IRules;
+  readonly?: boolean;
+  onChange: (leftKey: string) => void;
+}
+
+export function useLeft({ left, options, userRules, readonly, onChange }: HookParams) {
+  const rules = useMemo(() => ({ ...defaultRules, ...(userRules || {}) }), [userRules]);
+
+  const typeManager = useTypeManager() as JsonSchemaTypeManager;
+
+  const rule = useMemo(() => {
+    if (!left) return undefined;
+
+    const option = options?.find((item) => item.value === left);
+
+    if (!option?.schema?.type) {
+      return undefined;
+    }
+
+    return rules[option.schema.type];
+  }, [left, options, rules]);
+
+  const renderDBOptionSelect = () => (
+    <UISelect
+      disabled={readonly}
+      size="small"
+      style={{ width: '100%' }}
+      value={left}
+      onChange={(v) => onChange(v as string)}
+      optionList={
+        options?.map((item) => ({
+          label: (
+            <UIOptionLabel>
+              <Icon size="small" svg={typeManager.getDisplayIcon(item.schema)} />
+              {item.label}
+            </UIOptionLabel>
+          ),
+          value: item.value,
+        })) || []
+      }
+    />
+  );
+
+  return { rule, renderDBOptionSelect };
+}

+ 59 - 0
packages/materials/form-materials/src/components/db-condition-row/hooks/use-op.tsx

@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React, { useMemo } from 'react';
+
+import { I18n } from '@flowgram.ai/editor';
+import { Button, Select } from '@douyinfe/semi-ui';
+import { IconChevronDownStroked } from '@douyinfe/semi-icons';
+
+import { ConditionRow } from '@/components';
+
+import { IRule, OpConfigs } from '../types';
+
+const defaultOpConfigs = ConditionRow.defaultOpConfigs;
+
+interface HookParams {
+  rule?: IRule;
+  op?: string;
+  onChange: (op: string) => void;
+  readonly?: boolean;
+  userOps?: OpConfigs;
+}
+
+export function useOp({ rule, op, onChange, readonly, userOps }: HookParams) {
+  const options = useMemo(
+    () =>
+      Object.keys(rule || {}).map((_op) => ({
+        ...(defaultOpConfigs[_op] || {}),
+        ...(userOps?.[_op] || {}),
+        value: _op,
+        label: I18n.t(userOps?.[_op]?.label || defaultOpConfigs[_op]?.label),
+      })),
+    [rule, userOps]
+  );
+
+  const opConfig = useMemo(() => defaultOpConfigs[op as string], [op]);
+
+  const renderOpSelect = () => (
+    <Select
+      style={{ height: 22 }}
+      disabled={readonly}
+      size="small"
+      value={op}
+      optionList={options}
+      onChange={(v) => {
+        onChange(v as string);
+      }}
+      triggerRender={({ value }) => (
+        <Button size="small" disabled={!rule}>
+          {opConfig?.abbreviation || <IconChevronDownStroked size="small" />}
+        </Button>
+      )}
+    />
+  );
+
+  return { renderOpSelect, opConfig };
+}

+ 93 - 0
packages/materials/form-materials/src/components/db-condition-row/index.tsx

@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React, { useMemo } from 'react';
+
+import { I18n } from '@flowgram.ai/editor';
+import { Input } from '@douyinfe/semi-ui';
+
+import { InjectDynamicValueInput } from '@/components/dynamic-value-input';
+
+import { DBConditionOptionType, DBConditionRowValueType, IRules, OpConfigs } from './types';
+import { UIContainer, UILeft, UIOperator, UIRight, UIValues } from './styles';
+import { useOp } from './hooks/use-op';
+import { useLeft } from './hooks/use-left';
+
+interface PropTypes {
+  value?: DBConditionRowValueType;
+  onChange: (value?: DBConditionRowValueType) => void;
+  style?: React.CSSProperties;
+  options?: DBConditionOptionType[];
+  readonly?: boolean;
+  ruleConfig?: {
+    ops?: OpConfigs;
+    rules?: IRules;
+  };
+}
+
+const defaultRuleConfig = {
+  ops: {},
+  rules: {},
+};
+
+export function DBConditionRow({
+  style,
+  value,
+  onChange,
+  readonly,
+  options,
+  ruleConfig = defaultRuleConfig,
+}: PropTypes) {
+  const { left, operator, right } = value || {};
+
+  const { rule, renderDBOptionSelect } = useLeft({
+    left,
+    options,
+    onChange: (leftKey) => onChange({ ...value, left: leftKey }),
+    readonly,
+    userRules: ruleConfig.rules,
+  });
+
+  const { renderOpSelect, opConfig } = useOp({
+    rule,
+    op: operator,
+    onChange: (v) => onChange({ ...value, operator: v }),
+    readonly,
+    userOps: ruleConfig.ops,
+  });
+
+  const targetSchema = useMemo(() => {
+    const targetType: string | null = rule?.[operator || ''] || null;
+    return targetType ? { type: targetType, extra: { weak: true } } : null;
+  }, [rule, opConfig]);
+
+  return (
+    <UIContainer style={style}>
+      <UIOperator>{renderOpSelect()}</UIOperator>
+      <UIValues>
+        <UILeft>{renderDBOptionSelect()}</UILeft>
+        <UIRight>
+          {targetSchema ? (
+            <InjectDynamicValueInput
+              readonly={readonly || !rule}
+              value={right}
+              schema={targetSchema}
+              onChange={(v) => onChange({ ...value, right: v })}
+            />
+          ) : (
+            <Input
+              size="small"
+              disabled
+              style={{ pointerEvents: 'none' }}
+              value={opConfig?.rightDisplay || I18n.t('Empty')}
+            />
+          )}
+        </UIRight>
+      </UIValues>
+    </UIContainer>
+  );
+}
+
+export { type DBConditionRowValueType, type DBConditionOptionType };

+ 43 - 0
packages/materials/form-materials/src/components/db-condition-row/styles.tsx

@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import styled from 'styled-components';
+import { Select } from '@douyinfe/semi-ui';
+
+export const UIContainer = styled.div`
+  display: flex;
+  align-items: center;
+  gap: 4px;
+`;
+
+export const UIOperator = styled.div``;
+
+export const UILeft = styled.div`
+  width: 100%;
+`;
+
+export const UIRight = styled.div`
+  width: 100%;
+`;
+
+export const UIValues = styled.div`
+  flex-grow: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 4px;
+`;
+
+export const UIOptionLabel = styled.div`
+  display: flex;
+  align-items: center;
+  gap: 10px;
+`;
+
+export const UISelect = styled(Select)`
+  & .semi-select-selection {
+    margin-left: 5px;
+  }
+`;

+ 34 - 0
packages/materials/form-materials/src/components/db-condition-row/types.ts

@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+
+import { IFlowConstantRefValue } from '@/typings';
+
+export interface OpConfig {
+  label: string;
+  abbreviation: string;
+  // When right is not a value, display this text
+  rightDisplay?: string;
+}
+
+export type OpConfigs = Record<string, OpConfig>;
+
+export type IRule = Partial<Record<string, string | null>>;
+
+export type IRules = Record<string, IRule>;
+
+export interface DBConditionRowValueType {
+  left?: string;
+  schema?: IJsonSchema;
+  operator?: string;
+  right?: IFlowConstantRefValue;
+}
+
+export interface DBConditionOptionType {
+  label: string | JSX.Element;
+  value: string;
+  schema: IJsonSchema;
+}

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

@@ -10,6 +10,7 @@ export * from './batch-variable-selector';
 export * from './constant-input';
 export * from './dynamic-value-input';
 export * from './condition-row';
+export * from './db-condition-row';
 export * from './batch-outputs';
 export * from './prompt-editor';
 export * from './prompt-editor-with-variables';