import React, { useContext, useEffect, useMemo, useState } from 'react'; import { createForm } from '@formily/core'; import { FormProvider, ISchema, Schema, useFieldSchema, useForm } from '@formily/react'; import { FormLayout } from '@formily/antd-v5'; import { Alert, Button, Modal, Space } from 'antd'; import { useTranslation } from 'react-i18next'; import { Action, ActionContextProvider, GeneralSchemaDesigner, InitializerWithSwitch, SchemaComponent, SchemaComponentContext, SchemaInitializer, SchemaInitializerItemOptions, SchemaInitializerProvider, SchemaSettings, VariableScopeProvider, gridRowColWrap, useCompile, useFormBlockContext, useSchemaOptionsContext, } from '@nocobase/client'; import { Registry, lodash } from '@nocobase/utils/client'; import { instructions, useAvailableUpstreams, useNodeContext } from '..'; import { JOB_STATUS } from '../../constants'; import { useFlowContext } from '../../FlowContext'; import { NAMESPACE, lang } from '../../locale'; import { useTrigger } from '../../triggers'; import { DetailsBlockProvider } from './DetailsBlockProvider'; import { FormBlockProvider } from './FormBlockProvider'; import createRecordForm from './forms/create'; import customRecordForm from './forms/custom'; import updateRecordForm from './forms/update'; import { useWorkflowVariableOptions } from '../../variable'; type ValueOf = T[keyof T]; export type FormType = { type: 'create' | 'update' | 'custom'; title: string; actions: ValueOf[]; collection: | string | { name: string; fields: any[]; [key: string]: any; }; }; export type ManualFormType = { title: string; config: { useInitializer: () => SchemaInitializerItemOptions; initializers?: { [key: string]: React.FC; }; components?: { [key: string]: React.FC; }; parseFormOptions(root: ISchema): { [key: string]: FormType }; }; block: { scope?: { [key: string]: () => any; }; components?: { [key: string]: React.FC; }; }; }; export const manualFormTypes = new Registry(); manualFormTypes.register('customForm', customRecordForm); manualFormTypes.register('createForm', createRecordForm); manualFormTypes.register('updateForm', updateRecordForm); function useTriggerInitializers(): SchemaInitializerItemOptions | null { const { workflow } = useFlowContext(); const trigger = useTrigger(); return trigger.useInitializers ? trigger.useInitializers(workflow.config) : null; } const blockTypeNames = { customForm: customRecordForm.title, record: `{{t("Data record", { ns: "${NAMESPACE}" })}}`, }; function SimpleDesigner() { const schema = useFieldSchema(); const title = blockTypeNames[schema['x-designer-props']?.type] ?? '{{t("Block")}}'; const compile = useCompile(); return ( ); } function AddBlockButton(props: any) { const current = useNodeContext(); const nodes = useAvailableUpstreams(current); const triggerInitializers = [useTriggerInitializers()].filter(Boolean); const nodeBlockInitializers = nodes .map((node) => { const instruction = instructions.get(node.type); return instruction?.useInitializers?.(node); }) .filter(Boolean); const dataBlockInitializers = [ ...triggerInitializers, ...(nodeBlockInitializers.length ? [ { key: 'nodes', type: 'subMenu', title: `{{t("Node result", { ns: "${NAMESPACE}" })}}`, children: nodeBlockInitializers, }, ] : []), ].filter(Boolean); const items = [ ...(dataBlockInitializers.length ? [ { type: 'itemGroup', title: '{{t("Data blocks")}}', children: dataBlockInitializers, }, ] : []), { type: 'itemGroup', title: '{{t("Form")}}', children: Array.from(manualFormTypes.getValues()).map((item: ManualFormType) => { const { useInitializer: getInitializer } = item.config; return getInitializer(); }), }, { type: 'itemGroup', title: '{{t("Other blocks")}}', children: [ { type: 'item', title: '{{t("Markdown")}}', component: 'MarkdownBlockInitializer', }, ], }, ] as SchemaInitializerItemOptions[]; return ; } function AssignedFieldValues() { const ctx = useContext(SchemaComponentContext); const { t } = useTranslation(); const fieldSchema = useFieldSchema(); const scope = useWorkflowVariableOptions({ fieldNames: { label: 'title', value: 'name' } }); const [open, setOpen] = useState(false); const [initialSchema, setInitialSchema] = useState(fieldSchema?.['x-action-settings']?.assignedValues?.schema ?? { type: 'void', 'x-component': 'Grid', 'x-initializer': 'CustomFormItemInitializers', properties: {}, }); const [schema, setSchema] = useState(null); const { components } = useSchemaOptionsContext(); useEffect(() => { setSchema(new Schema({ properties: { grid: initialSchema }, })); }, [initialSchema]); const form = useMemo( () => { const initialValues = fieldSchema?.['x-action-settings']?.assignedValues?.values; return createForm({ initialValues: lodash.cloneDeep(initialValues), values: lodash.cloneDeep(initialValues), }); }, [], ); const title = t('Assign field values'); function onCancel() { setOpen(false); } function onSubmit() { if (!fieldSchema['x-action-settings']) { fieldSchema['x-action-settings'] = {}; } if (!fieldSchema['x-action-settings'].assignedValues) { fieldSchema['x-action-settings'].assignedValues = {}; } fieldSchema['x-action-settings'].assignedValues.schema = initialSchema; fieldSchema['x-action-settings'].assignedValues.values = form.values; setOpen(false); setTimeout(() => { ctx.refresh?.(); }, 300); } return ( <> setOpen(true)}> {title} } >
{open && schema && ( )}
); } function ManualActionDesigner(props) { return ( ); } function ContinueInitializer({ action, actionProps, insert, ...props }) { return ( { insert({ type: 'void', title: props.title, 'x-decorator': 'ManualActionStatusProvider', 'x-decorator-props': { value: action, }, 'x-component': 'Action', 'x-component-props': { ...actionProps, useAction: '{{ useSubmit }}', }, 'x-designer': 'ManualActionDesigner', 'x-action-settings': {}, }); }} /> ); } function ActionInitializer({ action, actionProps, ...props }) { return ( ); } function AddActionButton(props) { return ( ); } // NOTE: fake useAction for ui configuration function useSubmit() { // const { values, submit, id: formId } = useForm(); // const formSchema = useFieldSchema(); return { run() {}, }; } export function SchemaConfig({ value, onChange }) { const ctx = useContext(SchemaComponentContext); const trigger = useTrigger(); const node = useNodeContext(); const nodes = useAvailableUpstreams(node); const form = useForm(); const { workflow } = useFlowContext(); const nodeInitializers = {}; const nodeComponents = {}; nodes.forEach((item) => { const instruction = instructions.get(item.type); Object.assign(nodeInitializers, instruction.initializers); Object.assign(nodeComponents, instruction.components); }); const schema = useMemo( () => new Schema({ properties: { drawer: { type: 'void', title: '{{t("Configure form")}}', 'x-decorator': 'Form', 'x-component': 'Action.Drawer', 'x-component-props': { className: 'nb-action-popup', }, properties: { tabs: { type: 'void', 'x-component': 'Tabs', 'x-component-props': {}, 'x-initializer': 'TabPaneInitializers', 'x-initializer-props': { gridInitializer: 'AddBlockButton', }, properties: value ?? { tab1: { type: 'void', title: `{{t("Manual", { ns: "${NAMESPACE}" })}}`, 'x-component': 'Tabs.TabPane', 'x-designer': 'Tabs.Designer', properties: { grid: { type: 'void', 'x-component': 'Grid', 'x-initializer': 'AddBlockButton', properties: {}, }, }, }, }, }, }, }, }, }), [], ); return ( Object.assign(result, item.config.parseFormOptions(tabs)), {}, ); form.setValuesIn('forms', forms); onChange(tabs.properties); }, }} > Object.assign(result, item.config.initializers), {}, ), }} > Object.assign(result, item.config.components), {}, ), FormBlockProvider, DetailsBlockProvider, // NOTE: fake provider component ManualActionStatusProvider(props) { return props.children; }, ActionBarProvider(props) { return props.children; }, SimpleDesigner, ManualActionDesigner, }} scope={{ useSubmit, useDetailsBlockProps: useFormBlockContext, }} /> ); } export function SchemaConfigButton(props) { const { workflow } = useFlowContext(); const [visible, setVisible] = useState(false); return ( <> {props.children} ); }