UNPKG

@nocobase/flow-engine

Version:

A standalone flow engine for NocoBase, managing workflows, models, and actions.

193 lines (163 loc) 6.29 kB
/** * This file is part of the NocoBase (R) project. * Copyright (c) 2020-2024 NocoBase Co., Ltd. * Authors: NocoBase Team. * * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. * For more information, please refer to: https://www.nocobase.com/agreement. */ import React from 'react'; import { Input } from 'antd'; import type { MetaTreeNode } from '../../flowContext'; import type { ContextSelectorItem, Converters } from './types'; import { isVariableExpression } from '../../utils'; export const parseValueToPath = (value: string): string[] | undefined => { if (typeof value !== 'string') return undefined; const trimmed = value.trim(); const variableRegex = /^\{\{\s*ctx(?:\.(.+?))?\s*\}\}$/; const match = trimmed.match(variableRegex); if (!match) return undefined; const pathString = match[1]; if (!pathString) return []; return pathString.split('.'); }; export const formatPathToValue = (item: MetaTreeNode): string => { const path = item?.paths || []; if (path.length === 0) return '{{ ctx }}'; return `{{ ctx.${path.join('.')} }}`; }; export const loadMetaTreeChildren = async (metaNode: MetaTreeNode): Promise<MetaTreeNode[]> => { if (!metaNode?.children) return []; if (typeof metaNode.children === 'function') { try { return await metaNode.children(); } catch (error) { console.warn(`Failed to load children for ${metaNode.name}:`, error); return []; } } return metaNode.children; }; // 在已加载的级联选项中搜索,支持模糊匹配 export const searchInLoadedNodes = ( options: ContextSelectorItem[], searchText: string, parentPaths: string[] = [], ): ContextSelectorItem[] => { if (!searchText || !searchText.trim()) return []; const lowerSearchText = searchText.toLowerCase().trim(); const results: ContextSelectorItem[] = []; // 递归搜索已加载的节点 const searchRecursive = (nodes: ContextSelectorItem[], currentPath: string[] = []) => { for (const node of nodes) { const nodePath = [...currentPath, node.value]; // 计算可搜索的纯文本标签 const labelText = typeof node.label === 'string' ? node.label : typeof node.meta?.title === 'string' ? node.meta!.title : String(node.value); // 检查节点标签是否匹配搜索文本 if (labelText.toLowerCase().includes(lowerSearchText)) { results.push(node); } // 只搜索已加载的子节点(存在children属性表示已加载) if (node.children && Array.isArray(node.children)) { searchRecursive(node.children, nodePath); } } }; searchRecursive(options, []); return results; }; export const buildContextSelectorItems = (metaTree: MetaTreeNode[]): ContextSelectorItem[] => { if (!metaTree || !Array.isArray(metaTree)) { console.warn('buildContextSelectorItems received invalid metaTree:', metaTree); return []; } const convertNode = (node: MetaTreeNode): ContextSelectorItem => { const hasChildren = !!( node.children && (typeof node.children === 'function' || (Array.isArray(node.children) && node.children.length > 0)) ); // 计算禁用状态:支持 boolean 或函数 const disabled = !!(typeof node.disabled === 'function' ? node.disabled() : node.disabled); const option: ContextSelectorItem = { label: node.title || node.name, value: node.name, isLeaf: !hasChildren, meta: node, paths: node.paths, disabled, }; if (Array.isArray(node.children) && node.children.length > 0) { option.children = node.children.map((child) => convertNode(child)); } return option; }; return metaTree.map((node) => convertNode(node)); }; /** * 预加载:根据路径逐级加载 ContextSelectorItem 的 children,保证打开时已展开对应层级。 */ export const preloadContextSelectorPath = async ( options: ContextSelectorItem[] | undefined, pathSegments: (string | number)[], triggerUpdate?: () => void, ) => { if (!options || !Array.isArray(options) || !pathSegments || pathSegments.length === 0) return; let list: ContextSelectorItem[] | undefined = options; for (let i = 0; i < pathSegments.length && list; i++) { const seg = String(pathSegments[i]); const opt = list.find((o) => String(o.value) === seg); if (!opt) break; const meta = opt.meta as MetaTreeNode | undefined; const hasLoaded = !!opt.children && Array.isArray(opt.children); if (i < pathSegments.length - 1 && !hasLoaded && meta && typeof meta.children === 'function') { opt.loading = true; try { const childNodes = await loadMetaTreeChildren(meta); meta.children = childNodes; const childOptions = buildContextSelectorItems(childNodes); opt.children = childOptions; opt.isLeaf = !childOptions || childOptions.length === 0; Promise.resolve() .then(() => triggerUpdate?.()) .catch(() => {}); } finally { opt.loading = false; } } list = Array.isArray(opt.children) ? opt.children : undefined; } }; export const isVariableValue = (value: any): boolean => { return isVariableExpression(value); }; export const createDefaultConverters = (): Converters => { return { resolvePathFromValue: (value: any) => { return parseValueToPath(value); }, resolveValueFromPath: (item: MetaTreeNode) => { return formatPathToValue(item); }, }; }; export const createFinalConverters = (propConverters?: Converters): Converters => { const defaultConverters = createDefaultConverters(); const mergedConverters = propConverters ? { ...defaultConverters, ...propConverters } : defaultConverters; // 如果用户自定义了 resolveValueFromPath,需要包装一下以保持后备逻辑 if (propConverters?.resolveValueFromPath) { const customResolveValueFromPath = propConverters.resolveValueFromPath; return { ...mergedConverters, resolveValueFromPath: (item: MetaTreeNode) => { const ret = customResolveValueFromPath(item); return ret === undefined ? formatPathToValue(item) : ret; }, }; } return mergedConverters; };