/** * 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, { createContext, useCallback, useContext, useMemo, useState } from 'react'; import { createForm } from '@formily/core'; import { observer, useForm } from '@formily/react'; import { ActionContextProvider, css, SchemaComponent, useActionContext, useAPIClient, useCancelAction, useCompile, usePlugin, } from '@nocobase/client'; import WorkflowPlugin, { Instruction, useStyles } from '.'; import { useFlowContext } from './FlowContext'; import { lang, NAMESPACE } from './locale'; import { RadioWithTooltip } from './components'; import { uid } from '@nocobase/utils/client'; import { Button, Dropdown } from 'antd'; import { PlusOutlined } from '@ant-design/icons'; interface AddButtonProps { upstream; branchIndex?: number | null; [key: string]: any; } export function AddButton(props: AddButtonProps) { const { upstream, branchIndex = null } = props; const engine = usePlugin(WorkflowPlugin); const compile = useCompile(); const { workflow } = useFlowContext() ?? {}; const instructionList = Array.from(engine.instructions.getValues()) as Instruction[]; const { styles } = useStyles(); const { onCreate, creating } = useAddNodeContext(); const groups = useMemo(() => { const result = [ { key: 'control', label: `{{t("Control", { ns: "${NAMESPACE}" })}}` }, { key: 'calculation', label: `{{t("Calculation", { ns: "${NAMESPACE}" })}}` }, { key: 'collection', label: `{{t("Collection operations", { ns: "${NAMESPACE}" })}}` }, { key: 'manual', label: `{{t("Manual", { ns: "${NAMESPACE}" })}}` }, { key: 'extended', label: `{{t("Extended types", { ns: "${NAMESPACE}" })}}` }, ] .map((group) => { const groupInstructions = instructionList.filter( (item) => item.group === group.key && (item.isAvailable ? item.isAvailable({ engine, workflow, upstream, branchIndex }) : true), ); return { ...group, type: 'group', children: groupInstructions.map((item) => ({ role: 'button', 'aria-label': item.type, key: item.type, label: item.title, })), }; }) .filter((group) => group.children.length); return compile(result); }, [branchIndex, compile, engine, instructionList, upstream, workflow]); const onClick = useCallback( async ({ keyPath }) => { const [type] = keyPath; onCreate({ type, upstream, branchIndex }); }, [branchIndex, onCreate, upstream], ); if (!workflow) { return null; } return (
); } function useAddNodeSubmitAction() { const form = useForm(); const api = useAPIClient(); const { workflow, refresh } = useFlowContext(); const { presetting, setPresetting, setCreating } = useAddNodeContext(); const ctx = useActionContext(); return { async run() { if (!presetting) { return; } await form.submit(); setCreating(presetting.data); try { const { data: { data: newNode }, } = await api.resource('workflows.nodes', workflow.id).create({ values: { ...presetting.data, config: { ...presetting.data.config, ...form.values.config, }, }, }); if (typeof form.values.downstreamBranchIndex === 'number' && newNode.downstreamId) { await api.resource('flow_nodes').update({ filterByTk: newNode.downstreamId, values: { branchIndex: form.values.downstreamBranchIndex, upstream: { id: newNode.id, downstreamId: null, }, }, updateAssociationValues: ['upstream'], }); } ctx.setVisible(false); setPresetting(null); refresh(); } catch (err) { console.error(err); } finally { setCreating(null); } }, }; } const AddNodeContext = createContext(null); export function useAddNodeContext() { return useContext(AddNodeContext); } const defaultBranchingOptions = [ { label: `{{t('After end of branches', { ns: "${NAMESPACE}" })}}`, value: false, }, { label: `{{t('Inside of branch', { ns: "${NAMESPACE}" })}}`, value: 0, }, ]; const DownstreamBranchIndex = observer((props) => { const { presetting } = useAddNodeContext(); const { nodes } = useFlowContext(); const { values } = useForm(); const options = useMemo(() => { if (!presetting?.instruction) { return []; } const { instruction, data } = presetting; const downstream = data.upstreamId ? nodes.find((item) => item.upstreamId === data.upstreamId && item.branchIndex === data.branchIndex) : nodes.find((item) => item.upstreamId === null); if (!downstream) { return []; } const branching = typeof instruction.branching === 'function' ? instruction.branching(values.config ?? {}) : instruction.branching; if (!branching) { return []; } return branching === true ? defaultBranchingOptions : branching; }, [presetting, nodes, values.config]); if (!options.length) { return null; } const { data } = presetting; return ( ); // return ( // // // // ); }); function PresetFieldset() { const { presetting } = useAddNodeContext(); if (!presetting?.instruction.presetFieldset) { return null; } return ( ); } export function AddNodeContextProvider(props) { const api = useAPIClient(); const compile = useCompile(); const engine = usePlugin(WorkflowPlugin); const [creating, setCreating] = useState(null); const [presetting, setPresetting] = useState(null); const [formValueChanged, setFormValueChanged] = useState(false); const { workflow, nodes, refresh } = useFlowContext() ?? {}; const form = useMemo(() => createForm(), []); const onModalCancel = useCallback( (visible) => { if (!visible) { form.reset(); form.clearFormGraph('*'); setPresetting(null); } }, [form], ); const create = useCallback( async (data) => { setCreating(data); try { await api.resource('workflows.nodes', workflow.id).create({ values: data, }); refresh(); } catch (err) { console.error(err); } finally { setCreating(null); } }, [api, refresh, workflow.id], ); const onCreate = useCallback( ({ type, upstream, branchIndex }) => { const instruction = engine.instructions.get(type); if (!instruction) { console.error(`Instruction "${type}" not found`); return; } const data = { key: uid(), type, upstreamId: upstream?.id ?? null, branchIndex, title: compile(instruction.title), config: instruction.createDefaultConfig?.() ?? {}, }; const downstream = upstream?.id ? nodes.find((item) => item.upstreamId === data.upstreamId && item.branchIndex === data.branchIndex) : nodes.find((item) => item.upstreamId === null); if ( instruction.presetFieldset || ((typeof instruction.branching === 'function' ? instruction.branching(data.config) : instruction.branching) && downstream) ) { setPresetting({ data, instruction }); return; } create(data); }, [compile, create, engine.instructions], ); return ( {props.children} ); }