/** * 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. */ /** * 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. * * @example * // 基本使用 * * * // 显示设置但隐藏删除按钮 * * * // 显示设置和title * * * // 使用右键菜单模式并隐藏删除按钮 * */ import { observer } from '@formily/reactive-react'; import { Spin } from 'antd'; import _ from 'lodash'; import React, { Suspense, useEffect } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; import { useApplyAutoFlows, FlowModelProvider } from '../hooks'; import { FlowModel } from '../models'; import { ToolbarItemConfig } from '../types'; import { FlowsContextMenu } from './settings/wrappers/contextual/FlowsContextMenu'; import { FlowsFloatContextMenu } from './settings/wrappers/contextual/FlowsFloatContextMenu'; import { FlowErrorFallback } from './FlowErrorFallback'; interface FlowModelRendererProps { model?: FlowModel; uid?: string; fallback?: React.ReactNode; // 渲染失败时的回退内容 /** 是否显示流程设置入口(如按钮、菜单等) */ showFlowSettings?: boolean | { showBackground?: boolean; showBorder?: boolean }; // 默认 false /** 流程设置的交互风格 */ flowSettingsVariant?: 'dropdown' | 'contextMenu' | 'modal' | 'drawer'; // 默认 'dropdown' /** 是否在设置中隐藏移除按钮 */ hideRemoveInSettings?: boolean; // 默认 false /** 是否在边框左上角显示模型title,默认 false */ showTitle?: boolean; // 默认 false /** 是否跳过自动应用流程,默认 false */ skipApplyAutoFlows?: boolean; // 默认 false /** 当 skipApplyAutoFlows !== false 时,传递给 useApplyAutoFlows 的额外上下文 */ extraContext?: Record; /** Model 共享运行上下文,会沿着 model 树向下传递 */ sharedContext?: Record; /** 是否在最外层包装 FlowErrorFallback 组件,默认 false */ showErrorFallback?: boolean; // 默认 false /** 设置菜单层级:1=仅当前模型(默认),2=包含子模型 */ settingsMenuLevel?: number; /** 额外的工具栏项目,仅应用于此实例 */ extraToolbarItems?: ToolbarItemConfig[]; } /** * 内部组件:带有 useApplyAutoFlows 的渲染器 */ const FlowModelRendererWithAutoFlows: React.FC<{ model: FlowModel; showFlowSettings: boolean | { showBackground?: boolean; showBorder?: boolean }; flowSettingsVariant: string; hideRemoveInSettings: boolean; showTitle: boolean; extraContext?: Record; sharedContext?: Record; showErrorFallback?: boolean; settingsMenuLevel?: number; extraToolbarItems?: ToolbarItemConfig[]; }> = observer( ({ model, showFlowSettings, flowSettingsVariant, hideRemoveInSettings, showTitle, extraContext, sharedContext, showErrorFallback, settingsMenuLevel, extraToolbarItems, }) => { useApplyAutoFlows(model, extraContext); return ( ); }, ); /** * 内部组件:不带 useApplyAutoFlows 的渲染器 */ const FlowModelRendererWithoutAutoFlows: React.FC<{ model: FlowModel; showFlowSettings: boolean | { showBackground?: boolean; showBorder?: boolean }; flowSettingsVariant: string; hideRemoveInSettings: boolean; showTitle: boolean; sharedContext?: Record; showErrorFallback?: boolean; settingsMenuLevel?: number; extraToolbarItems?: ToolbarItemConfig[]; }> = observer( ({ model, showFlowSettings, flowSettingsVariant, hideRemoveInSettings, showTitle, sharedContext, showErrorFallback, settingsMenuLevel, extraToolbarItems, }) => { return ( ); }, ); /** * 核心渲染逻辑组件 */ const FlowModelRendererCore: React.FC<{ model: FlowModel; showFlowSettings: boolean | { showBackground?: boolean; showBorder?: boolean }; flowSettingsVariant: string; hideRemoveInSettings: boolean; showTitle: boolean; showErrorFallback?: boolean; settingsMenuLevel?: number; extraToolbarItems?: ToolbarItemConfig[]; }> = observer( ({ model, showFlowSettings, flowSettingsVariant, hideRemoveInSettings, showTitle, showErrorFallback, settingsMenuLevel, extraToolbarItems, }) => { // 渲染模型内容 const modelContent = model.render(); // 包装 ErrorBoundary 的辅助函数 const wrapWithErrorBoundary = (children: React.ReactNode) => { if (showErrorFallback) { return {children}; } return children; }; // 如果不显示流程设置,直接返回模型内容(可能包装 ErrorBoundary) if (!showFlowSettings) { return wrapWithErrorBoundary(modelContent); } // 根据 flowSettingsVariant 包装相应的设置组件 switch (flowSettingsVariant) { case 'dropdown': return ( {wrapWithErrorBoundary(modelContent)} ); case 'contextMenu': return ( {wrapWithErrorBoundary(modelContent)} ); case 'modal': // TODO: 实现 modal 模式的流程设置 console.warn('FlowModelRenderer: modal variant is not implemented yet'); return wrapWithErrorBoundary(modelContent); case 'drawer': // TODO: 实现 drawer 模式的流程设置 console.warn('FlowModelRenderer: drawer variant is not implemented yet'); return wrapWithErrorBoundary(modelContent); default: console.warn( `FlowModelRenderer: Unknown flowSettingsVariant '${flowSettingsVariant}', falling back to dropdown`, ); return ( {wrapWithErrorBoundary(modelContent)} ); } }, ); /** * A React component responsible for rendering a FlowModel. * It delegates the actual rendering logic to the `render` method of the provided model. * * @param {FlowModelRendererProps} props - The component's props. * @param {FlowModel} props.model - The FlowModel instance to render. * @param {string} props.uid - The unique identifier for the flow model. * @param {boolean} props.showFlowSettings - Whether to show flow settings entry (buttons, menus, etc.). * @param {string} props.flowSettingsVariant - The interaction style for flow settings. * @param {boolean} props.hideRemoveInSettings - Whether to hide remove button in settings. * @param {boolean} props.showTitle - Whether to show model title in the top-left corner of the border. * @param {boolean} props.skipApplyAutoFlows - Whether to skip applying auto flows. * @param {any} props.extraContext - Extra context to pass to useApplyAutoFlows when skipApplyAutoFlows is false. * @param {any} props.sharedContext - Shared context to pass to the model. * @param {number} props.settingsMenuLevel - Settings menu levels: 1=current model only (default), 2=include sub-models. * @param {ToolbarItemConfig[]} props.extraToolbarItems - Extra toolbar items to add to this renderer instance. * @returns {React.ReactNode | null} The rendered output of the model, or null if the model or its render method is invalid. */ export const FlowModelRenderer: React.FC = observer( ({ model, fallback = , showFlowSettings = false, flowSettingsVariant = 'dropdown', hideRemoveInSettings = false, showTitle = false, skipApplyAutoFlows = false, extraContext, sharedContext, showErrorFallback = false, settingsMenuLevel, extraToolbarItems, }) => { if (!model || typeof model.render !== 'function') { // 可以选择渲染 null 或者一个错误/提示信息 console.warn('FlowModelRenderer: Invalid model or render method not found.', model); return null; } useEffect(() => { model.setSharedContext(sharedContext); }, [model, sharedContext]); // 根据 skipApplyAutoFlows 选择不同的内部组件 if (skipApplyAutoFlows) { return ( }> ); } else { return ( ); } }, );