feat: add settingsMenuLevel parameter to control menu hierarchy

This commit is contained in:
gchust 2025-06-25 15:50:06 +08:00
parent a8546e3599
commit 75b4de9b07
2 changed files with 98 additions and 57 deletions

View File

@ -75,6 +75,9 @@ interface FlowModelRendererProps {
/** 是否在最外层包装 FlowErrorFallback 组件,默认 false */ /** 是否在最外层包装 FlowErrorFallback 组件,默认 false */
showErrorFallback?: boolean; // 默认 false showErrorFallback?: boolean; // 默认 false
/** 设置菜单层级1=仅当前模型(默认)2=包含子模型 */
settingsMenuLevel?: number;
} }
/** /**
@ -89,6 +92,7 @@ const FlowModelRendererWithAutoFlows: React.FC<{
sharedContext?: Record<string, any>; sharedContext?: Record<string, any>;
independentAutoFlowExecution?: boolean; independentAutoFlowExecution?: boolean;
showErrorFallback?: boolean; showErrorFallback?: boolean;
settingsMenuLevel?: number;
}> = observer( }> = observer(
({ ({
model, model,
@ -99,6 +103,7 @@ const FlowModelRendererWithAutoFlows: React.FC<{
sharedContext, sharedContext,
independentAutoFlowExecution, independentAutoFlowExecution,
showErrorFallback, showErrorFallback,
settingsMenuLevel,
}) => { }) => {
useApplyAutoFlows(model, extraContext, !independentAutoFlowExecution); useApplyAutoFlows(model, extraContext, !independentAutoFlowExecution);
@ -110,6 +115,7 @@ const FlowModelRendererWithAutoFlows: React.FC<{
flowSettingsVariant={flowSettingsVariant} flowSettingsVariant={flowSettingsVariant}
hideRemoveInSettings={hideRemoveInSettings} hideRemoveInSettings={hideRemoveInSettings}
showErrorFallback={showErrorFallback} showErrorFallback={showErrorFallback}
settingsMenuLevel={settingsMenuLevel}
/> />
</FlowModelProvider> </FlowModelProvider>
); );
@ -126,8 +132,17 @@ const FlowModelRendererWithoutAutoFlows: React.FC<{
hideRemoveInSettings: boolean; hideRemoveInSettings: boolean;
sharedContext?: Record<string, any>; sharedContext?: Record<string, any>;
showErrorFallback?: boolean; showErrorFallback?: boolean;
settingsMenuLevel?: number;
}> = observer( }> = observer(
({ model, showFlowSettings, flowSettingsVariant, hideRemoveInSettings, sharedContext, showErrorFallback }) => { ({
model,
showFlowSettings,
flowSettingsVariant,
hideRemoveInSettings,
sharedContext,
showErrorFallback,
settingsMenuLevel,
}) => {
return ( return (
<FlowModelProvider model={model}> <FlowModelProvider model={model}>
<FlowModelRendererCore <FlowModelRendererCore
@ -136,6 +151,7 @@ const FlowModelRendererWithoutAutoFlows: React.FC<{
flowSettingsVariant={flowSettingsVariant} flowSettingsVariant={flowSettingsVariant}
hideRemoveInSettings={hideRemoveInSettings} hideRemoveInSettings={hideRemoveInSettings}
showErrorFallback={showErrorFallback} showErrorFallback={showErrorFallback}
settingsMenuLevel={settingsMenuLevel}
/> />
</FlowModelProvider> </FlowModelProvider>
); );
@ -151,68 +167,75 @@ const FlowModelRendererCore: React.FC<{
flowSettingsVariant: string; flowSettingsVariant: string;
hideRemoveInSettings: boolean; hideRemoveInSettings: boolean;
showErrorFallback?: boolean; showErrorFallback?: boolean;
}> = observer(({ model, showFlowSettings, flowSettingsVariant, hideRemoveInSettings, showErrorFallback }) => { settingsMenuLevel?: number;
// 渲染模型内容 }> = observer(
const modelContent = model.render(); ({ model, showFlowSettings, flowSettingsVariant, hideRemoveInSettings, showErrorFallback, settingsMenuLevel }) => {
// 渲染模型内容
const modelContent = model.render();
// 包装 ErrorBoundary 的辅助函数 // 包装 ErrorBoundary 的辅助函数
const wrapWithErrorBoundary = (children: React.ReactNode) => { const wrapWithErrorBoundary = (children: React.ReactNode) => {
if (showErrorFallback) { if (showErrorFallback) {
return <ErrorBoundary FallbackComponent={FlowErrorFallback}>{children}</ErrorBoundary>; return <ErrorBoundary FallbackComponent={FlowErrorFallback}>{children}</ErrorBoundary>;
}
return children;
};
// 如果不显示流程设置,直接返回模型内容(可能包装 ErrorBoundary
if (!showFlowSettings) {
return wrapWithErrorBoundary(modelContent);
} }
return children;
};
// 如果不显示流程设置,直接返回模型内容(可能包装 ErrorBoundary // 根据 flowSettingsVariant 包装相应的设置组件
if (!showFlowSettings) { switch (flowSettingsVariant) {
return wrapWithErrorBoundary(modelContent); case 'dropdown':
} return (
<FlowsFloatContextMenu
model={model}
showDeleteButton={!hideRemoveInSettings}
showBackground={_.isObject(showFlowSettings) ? showFlowSettings.showBackground : undefined}
showBorder={_.isObject(showFlowSettings) ? showFlowSettings.showBorder : undefined}
settingsMenuLevel={settingsMenuLevel}
>
{wrapWithErrorBoundary(modelContent)}
</FlowsFloatContextMenu>
);
// 根据 flowSettingsVariant 包装相应的设置组件 case 'contextMenu':
switch (flowSettingsVariant) { return (
case 'dropdown': <FlowsContextMenu model={model} showDeleteButton={!hideRemoveInSettings}>
return ( {wrapWithErrorBoundary(modelContent)}
<FlowsFloatContextMenu </FlowsContextMenu>
model={model} );
showDeleteButton={!hideRemoveInSettings}
showBackground={_.isObject(showFlowSettings) ? showFlowSettings.showBackground : undefined}
showBorder={_.isObject(showFlowSettings) ? showFlowSettings.showBorder : undefined}
>
{wrapWithErrorBoundary(modelContent)}
</FlowsFloatContextMenu>
);
case 'contextMenu': case 'modal':
return ( // TODO: 实现 modal 模式的流程设置
<FlowsContextMenu model={model} showDeleteButton={!hideRemoveInSettings}> console.warn('FlowModelRenderer: modal variant is not implemented yet');
{wrapWithErrorBoundary(modelContent)} return wrapWithErrorBoundary(modelContent);
</FlowsContextMenu>
);
case 'modal': case 'drawer':
// TODO: 实现 modal 模式的流程设置 // TODO: 实现 drawer 模式的流程设置
console.warn('FlowModelRenderer: modal variant is not implemented yet'); console.warn('FlowModelRenderer: drawer variant is not implemented yet');
return wrapWithErrorBoundary(modelContent); return wrapWithErrorBoundary(modelContent);
case 'drawer': default:
// TODO: 实现 drawer 模式的流程设置 console.warn(
console.warn('FlowModelRenderer: drawer variant is not implemented yet'); `FlowModelRenderer: Unknown flowSettingsVariant '${flowSettingsVariant}', falling back to dropdown`,
return wrapWithErrorBoundary(modelContent); );
return (
default: <FlowsFloatContextMenu
console.warn(`FlowModelRenderer: Unknown flowSettingsVariant '${flowSettingsVariant}', falling back to dropdown`); model={model}
return ( showDeleteButton={!hideRemoveInSettings}
<FlowsFloatContextMenu showBackground={_.isObject(showFlowSettings) ? showFlowSettings.showBackground : undefined}
model={model} showBorder={_.isObject(showFlowSettings) ? showFlowSettings.showBorder : undefined}
showDeleteButton={!hideRemoveInSettings} settingsMenuLevel={settingsMenuLevel}
showBackground={_.isObject(showFlowSettings) ? showFlowSettings.showBackground : undefined} >
showBorder={_.isObject(showFlowSettings) ? showFlowSettings.showBorder : undefined} {wrapWithErrorBoundary(modelContent)}
> </FlowsFloatContextMenu>
{wrapWithErrorBoundary(modelContent)} );
</FlowsFloatContextMenu> }
); },
} );
});
/** /**
* A React component responsible for rendering a FlowModel. * A React component responsible for rendering a FlowModel.
@ -228,6 +251,7 @@ const FlowModelRendererCore: React.FC<{
* @param {any} props.extraContext - Extra context to pass to useApplyAutoFlows when skipApplyAutoFlows is false. * @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 {any} props.sharedContext - Shared context to pass to the model.
* @param {boolean} props.independentAutoFlowExecution - Whether each component has independent auto flow execution. * @param {boolean} props.independentAutoFlowExecution - Whether each component has independent auto flow execution.
* @param {number} props.settingsMenuLevel - Settings menu levels: 1=current model only (default), 2=include sub-models.
* @returns {React.ReactNode | null} The rendered output of the model, or null if the model or its render method is invalid. * @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<FlowModelRendererProps> = observer( export const FlowModelRenderer: React.FC<FlowModelRendererProps> = observer(
@ -242,6 +266,7 @@ export const FlowModelRenderer: React.FC<FlowModelRendererProps> = observer(
sharedContext, sharedContext,
independentAutoFlowExecution = false, independentAutoFlowExecution = false,
showErrorFallback = false, showErrorFallback = false,
settingsMenuLevel,
}) => { }) => {
if (!model || typeof model.render !== 'function') { if (!model || typeof model.render !== 'function') {
// 可以选择渲染 null 或者一个错误/提示信息 // 可以选择渲染 null 或者一个错误/提示信息
@ -264,6 +289,7 @@ export const FlowModelRenderer: React.FC<FlowModelRendererProps> = observer(
hideRemoveInSettings={hideRemoveInSettings} hideRemoveInSettings={hideRemoveInSettings}
sharedContext={sharedContext} sharedContext={sharedContext}
showErrorFallback={showErrorFallback} showErrorFallback={showErrorFallback}
settingsMenuLevel={settingsMenuLevel}
/> />
</Suspense> </Suspense>
); );
@ -279,6 +305,7 @@ export const FlowModelRenderer: React.FC<FlowModelRendererProps> = observer(
sharedContext={sharedContext} sharedContext={sharedContext}
independentAutoFlowExecution={independentAutoFlowExecution} independentAutoFlowExecution={independentAutoFlowExecution}
showErrorFallback={showErrorFallback} showErrorFallback={showErrorFallback}
settingsMenuLevel={settingsMenuLevel}
/> />
</Suspense> </Suspense>
); );

View File

@ -40,6 +40,7 @@ const renderToolbarItems = (
showDeleteButton: boolean, showDeleteButton: boolean,
showCopyUidButton: boolean, showCopyUidButton: boolean,
flowEngine: FlowEngine, flowEngine: FlowEngine,
settingsMenuLevel?: number,
) => { ) => {
const toolbarItems = flowEngine?.flowSettings?.getToolbarItems?.() || []; const toolbarItems = flowEngine?.flowSettings?.getToolbarItems?.() || [];
@ -60,6 +61,7 @@ const renderToolbarItems = (
model={model} model={model}
showDeleteButton={showDeleteButton} showDeleteButton={showDeleteButton}
showCopyUidButton={showCopyUidButton} showCopyUidButton={showCopyUidButton}
menuLevels={settingsMenuLevel}
/> />
); );
} }
@ -144,6 +146,10 @@ interface ModelProvidedProps {
* @default true * @default true
*/ */
showBackground?: boolean; showBackground?: boolean;
/**
* Settings menu levels: 1=current model only (default), 2=include sub-models
*/
settingsMenuLevel?: number;
} }
interface ModelByIdProps { interface ModelByIdProps {
@ -163,6 +169,10 @@ interface ModelByIdProps {
* @default true * @default true
*/ */
showBackground?: boolean; showBackground?: boolean;
/**
* Settings menu levels: 1=current model only (default), 2=include sub-models
*/
settingsMenuLevel?: number;
} }
type FlowsFloatContextMenuProps = ModelProvidedProps | ModelByIdProps; type FlowsFloatContextMenuProps = ModelProvidedProps | ModelByIdProps;
@ -193,6 +203,7 @@ const isModelByIdProps = (props: FlowsFloatContextMenuProps): props is ModelById
* @param props.showCopyUidButton UID按钮true * @param props.showCopyUidButton UID按钮true
* @param props.containerStyle * @param props.containerStyle
* @param props.className * @param props.className
* @param props.settingsMenuLevel 1=()2=
*/ */
const FlowsFloatContextMenu: React.FC<FlowsFloatContextMenuProps> = observer((props) => { const FlowsFloatContextMenu: React.FC<FlowsFloatContextMenuProps> = observer((props) => {
const flowEngine = useFlowEngine(); const flowEngine = useFlowEngine();
@ -219,6 +230,7 @@ const FlowsFloatContextMenuWithModel: React.FC<ModelProvidedProps> = observer(
className, className,
showBackground = true, showBackground = true,
showBorder = true, showBorder = true,
settingsMenuLevel,
}: ModelProvidedProps) => { }: ModelProvidedProps) => {
const [hideMenu, setHideMenu] = useState<boolean>(false); const [hideMenu, setHideMenu] = useState<boolean>(false);
const [hasButton, setHasButton] = useState<boolean>(false); const [hasButton, setHasButton] = useState<boolean>(false);
@ -293,7 +305,7 @@ const FlowsFloatContextMenuWithModel: React.FC<ModelProvidedProps> = observer(
<div className="general-schema-designer"> <div className="general-schema-designer">
<div className="general-schema-designer-icons"> <div className="general-schema-designer-icons">
<Space size={3} align="center"> <Space size={3} align="center">
{renderToolbarItems(model, showDeleteButton, showCopyUidButton, flowEngine)} {renderToolbarItems(model, showDeleteButton, showCopyUidButton, flowEngine, settingsMenuLevel)}
</Space> </Space>
</div> </div>
</div> </div>
@ -313,6 +325,7 @@ const FlowsFloatContextMenuWithModelById: React.FC<ModelByIdProps> = observer(
showCopyUidButton = true, showCopyUidButton = true,
containerStyle, containerStyle,
className, className,
settingsMenuLevel,
}) => { }) => {
const model = useFlowModelById(uid, modelClassName); const model = useFlowModelById(uid, modelClassName);
@ -328,6 +341,7 @@ const FlowsFloatContextMenuWithModelById: React.FC<ModelByIdProps> = observer(
showCopyUidButton={showCopyUidButton} showCopyUidButton={showCopyUidButton}
containerStyle={containerStyle} containerStyle={containerStyle}
className={className} className={className}
settingsMenuLevel={settingsMenuLevel}
> >
{children} {children}
</FlowsFloatContextMenuWithModel> </FlowsFloatContextMenuWithModel>