refactor(container): allow user to customize render content component

This commit is contained in:
liuyangxing 2025-03-18 15:11:37 +08:00
parent 429164671e
commit 088368d48c
18 changed files with 71 additions and 23 deletions

View File

@ -38,7 +38,9 @@ export const BaseNode = ({ node }: { node: FlowNodeEntity }) => {
outline: form?.state.invalid ? '1px solid red' : 'none',
}}
>
<NodeRenderContext.Provider value={{}}>{form?.render()}</NodeRenderContext.Provider>
<NodeRenderContext.Provider value={nodeRender}>
{form?.render()}
</NodeRenderContext.Provider>
</BaseNodeStyle>
</WorkflowNodeRenderer>
</ConfigProvider>

View File

@ -0,0 +1,14 @@
import { useNodeRender } from '@flowgram.ai/free-layout-editor';
import { ContainerNodeForm } from '@flowgram.ai/free-container-plugin';
import { NodeRenderContext } from '../../context';
export const ContainerNodeContent = () => {
const nodeRender = useNodeRender();
return (
<NodeRenderContext.Provider value={nodeRender}>
<ContainerNodeForm />;
</NodeRenderContext.Provider>
);
};

View File

@ -1,3 +1,4 @@
export * from './base-node';
export * from './line-add-button';
export * from './node-panel';
export * from './container-content';

View File

@ -1,6 +1,8 @@
import React from 'react';
interface INodeRenderContext {}
import type { NodeRenderReturnType } from '@flowgram.ai/free-layout-editor';
interface INodeRenderContext extends NodeRenderReturnType {}
/** 业务自定义节点上下文 */
export const NodeRenderContext = React.createContext<INodeRenderContext>({});
export const NodeRenderContext = React.createContext<INodeRenderContext>({} as INodeRenderContext);

View File

@ -1,7 +1,8 @@
import React from 'react';
import { FlowNodeRegistry, useNodeRender } from '@flowgram.ai/free-layout-editor';
import { FlowNodeRegistry } from '@flowgram.ai/free-layout-editor';
import { useNodeRenderContext } from '../../hooks';
import { FormTitleDescription, FormWrapper } from './styles';
/**
@ -9,7 +10,7 @@ import { FormTitleDescription, FormWrapper } from './styles';
* @constructor
*/
export function FormContent(props: { children?: React.ReactNode }) {
const { expanded, node } = useNodeRender();
const { expanded, node } = useNodeRenderContext();
const registry = node.getNodeRegistry<FlowNodeRegistry>();
return (
<FormWrapper>

View File

@ -5,7 +5,6 @@ import {
Field,
FieldRenderProps,
useClientContext,
useNodeRender,
useService,
} from '@flowgram.ai/free-layout-editor';
import { NodeIntoContainerService } from '@flowgram.ai/free-container-plugin';
@ -14,6 +13,7 @@ import { IconMore } from '@douyinfe/semi-icons';
import { Feedback } from '../feedback';
import { FlowNodeRegistry } from '../../typings';
import { useNodeRenderContext } from '../../hooks';
import { getIcon } from './utils';
import { Header, Operators, Title } from './styles';
@ -21,7 +21,7 @@ const { Text } = Typography;
function DropdownButton() {
const [key, setKey] = useState(0);
const { node, deleteNode } = useNodeRender();
const { node, deleteNode } = useNodeRenderContext();
const clientContext = useClientContext();
const registry = node.getNodeRegistry<FlowNodeRegistry>();
const nodeIntoContainerService = useService<NodeIntoContainerService>(NodeIntoContainerService);
@ -76,7 +76,7 @@ function DropdownButton() {
}
export function FormHeader() {
const { node, readonly } = useNodeRender();
const { node, readonly } = useNodeRenderContext();
return (
<Header>

View File

@ -1,12 +1,13 @@
import { Field, useNodeRender } from '@flowgram.ai/free-layout-editor';
import { Field } from '@flowgram.ai/free-layout-editor';
import { FxExpression } from '../fx-expression';
import { FormItem } from '../form-item';
import { Feedback } from '../feedback';
import { JsonSchema } from '../../typings';
import { useNodeRenderContext } from '../../hooks';
export function FormInputs() {
const { readonly } = useNodeRender();
const { readonly } = useNodeRenderContext();
return (
<Field<JsonSchema> name="inputs">
{({ field: inputsField }) => {

View File

@ -1,10 +1,10 @@
import React, { useState } from 'react';
import { useNodeRender } from '@flowgram.ai/free-layout-editor';
import { Button } from '@douyinfe/semi-ui';
import { IconPlus } from '@douyinfe/semi-icons';
import { JsonSchema } from '../../typings';
import { useNodeRenderContext } from '../../hooks';
import { PropertyEdit } from './property-edit';
export interface PropertiesEditProps {
@ -15,7 +15,7 @@ export interface PropertiesEditProps {
export const PropertiesEdit: React.FC<PropertiesEditProps> = (props) => {
const value = (props.value || {}) as Record<string, JsonSchema>;
const { readonly } = useNodeRender();
const { readonly } = useNodeRenderContext();
const [newProperty, updateNewPropertyFromCache] = useState<{ key: string; value: JsonSchema }>({
key: '',
value: { type: 'string' },

View File

@ -1 +1,2 @@
export { useEditorProps } from './use-editor-props';
export { useNodeRenderContext } from './use-node-render-context';

View File

@ -17,6 +17,7 @@ import { createSyncVariablePlugin } from '../plugins';
import { defaultFormMeta } from '../nodes/default-form-meta';
import { SelectorBoxPopover } from '../components/selector-box-popover';
import { BaseNode, LineAddButton, NodePanel } from '../components';
import { ContainerNodeContent } from '../components';
export function useEditorProps(
initialData: FlowDocumentJSON,
@ -200,7 +201,9 @@ export function useEditorProps(
createFreeNodePanelPlugin({
renderer: NodePanel,
}),
createContainerNodePlugin({}),
createContainerNodePlugin({
renderContent: <ContainerNodeContent />,
}),
],
}),
[]

View File

@ -0,0 +1,5 @@
import { useContext } from 'react';
import { NodeRenderContext } from '../context';
export const useNodeRenderContext = () => useContext(NodeRenderContext);

View File

@ -1,9 +1,10 @@
import { nanoid } from 'nanoid';
import { Field, FieldArray, useNodeRender } from '@flowgram.ai/free-layout-editor';
import { Field, FieldArray } from '@flowgram.ai/free-layout-editor';
import { Button } from '@douyinfe/semi-ui';
import { IconPlus, IconCrossCircleStroked } from '@douyinfe/semi-icons';
import { FlowLiteralValueSchema, FlowRefValueSchema } from '../../../typings';
import { useNodeRenderContext } from '../../../hooks';
import { FxExpression } from '../../../form-components/fx-expression';
import { FormItem } from '../../../form-components';
import { Feedback } from '../../../form-components';
@ -15,7 +16,7 @@ interface ConditionValue {
}
export function ConditionInputs() {
const { readonly } = useNodeRender();
const { readonly } = useNodeRenderContext();
return (
<FieldArray name="inputsValues.conditions">
{({ field }) => (

View File

@ -0,0 +1,5 @@
import React from 'react';
import { NodeRenderReturnType } from './typings';
export const NodeRenderContext = React.createContext<NodeRenderReturnType>({} as any);

View File

@ -1,3 +1,4 @@
export { ContainerNodeRenderKey } from './constant';
export { ContainerNodeRender } from './render';
export type { ContainerNodeMetaRenderProps, ContainerNodeRenderProps } from './type';
export * from './components';

View File

@ -7,16 +7,13 @@ import {
ContainerNodePorts,
ContainerNodeBorder,
ContainerNodeContainer,
ContainerNodeForm,
} from './components';
export const ContainerNodeRender: FC<ContainerNodeRenderProps> = () => (
export const ContainerNodeRender: FC<ContainerNodeRenderProps> = ({ content }) => (
<ContainerNodeContainer>
<ContainerNodeBackground />
<ContainerNodeBorder />
<ContainerNodeHeader>
<ContainerNodeForm />
</ContainerNodeHeader>
<ContainerNodeHeader>{content}</ContainerNodeHeader>
<ContainerNodePorts />
</ContainerNodeContainer>
);

View File

@ -1,3 +1,5 @@
import { ReactNode } from 'react';
import type { WorkflowNodeEntity } from '@flowgram.ai/free-layout-core';
export interface ContainerNodeMetaRenderProps {
@ -13,4 +15,5 @@ export interface ContainerNodeMetaRenderProps {
export interface ContainerNodeRenderProps {
node: WorkflowNodeEntity;
content?: ReactNode;
}

View File

@ -1,19 +1,27 @@
import React from 'react';
import { FlowRendererRegistry } from '@flowgram.ai/renderer';
import { definePluginCreator } from '@flowgram.ai/core';
import type { WorkflowContainerPluginOptions } from './type';
import { NodeIntoContainerService } from './node-into-container';
import { ContainerNodeRenderKey, ContainerNodeRender } from './container-node-render';
import {
ContainerNodeRenderKey,
ContainerNodeRender,
ContainerNodeRenderProps,
} from './container-node-render';
export const createContainerNodePlugin = definePluginCreator<WorkflowContainerPluginOptions>({
onBind: ({ bind }) => {
bind(NodeIntoContainerService).toSelf().inSingletonScope();
},
onInit(ctx) {
onInit(ctx, options) {
ctx.get(NodeIntoContainerService).init();
const registry = ctx.get<FlowRendererRegistry>(FlowRendererRegistry);
registry.registerReactComponent(ContainerNodeRenderKey, ContainerNodeRender);
registry.registerReactComponent(ContainerNodeRenderKey, (props: ContainerNodeRenderProps) => (
<ContainerNodeRender {...props} content={options.renderContent} />
));
},
onReady(ctx, options) {
if (options.disableNodeIntoContainer !== true) {

View File

@ -1,3 +1,6 @@
import type { ReactNode } from 'react';
export interface WorkflowContainerPluginOptions {
disableNodeIntoContainer?: boolean;
renderContent?: ReactNode;
}