import React, { useState, useContext } from 'react'; import { CloseOutlined, DeleteOutlined, } from '@ant-design/icons'; import { css, cx } from '@emotion/css'; import { ISchema, useForm } from '@formily/react'; import { Button, message, Modal, Tag, Alert, Input } from 'antd'; import { useTranslation } from 'react-i18next'; import parse from 'json-templates'; import { Registry } from '@nocobase/utils/client'; import { ActionContext, SchemaComponent, SchemaInitializerItemOptions, useActionContext, useAPIClient, useCompile, useRequest, useResourceActionContext } from '@nocobase/client'; import { nodeBlockClass, nodeCardClass, nodeClass, nodeMetaClass, nodeTitleClass } from '../style'; import { AddButton } from '../AddButton'; import { useFlowContext } from '../FlowContext'; import calculation from './calculation'; import condition from './condition'; import parallel from './parallel'; import delay from './delay'; import manual from './manual'; import query from './query'; import create from './create'; import update from './update'; import destroy from './destroy'; import { JobStatusOptions, JobStatusOptionsMap } from '../constants'; import { lang, NAMESPACE } from '../locale'; import request from "./request"; import { VariableOption } from '../variable'; export interface Instruction { title: string; type: string; group: string; options?: { label: string; value: any; key: string }[]; fieldset: { [key: string]: ISchema }; view?: ISchema; scope?: { [key: string]: any }; components?: { [key: string]: any }; render?(props): React.ReactNode; endding?: boolean; getOptions?(config, types?): VariableOption[] | null; useInitializers?(node): SchemaInitializerItemOptions | null; initializers?: { [key: string]: any }; }; export const instructions = new Registry(); instructions.register('condition', condition); instructions.register('parallel', parallel); instructions.register('calculation', calculation); instructions.register('delay', delay); instructions.register('manual', manual); instructions.register('query', query); instructions.register('create', create); instructions.register('update', update); instructions.register('destroy', destroy); instructions.register('request', request); function useUpdateAction() { const form = useForm(); const api = useAPIClient(); const ctx = useActionContext(); const { refresh } = useResourceActionContext(); const data = useNodeContext(); const { workflow } = useFlowContext(); return { async run() { if (workflow.executed) { message.error(lang('Node in executed workflow cannot be modified')); return; } await form.submit(); await api.resource('flow_nodes', data.id).update?.({ filterByTk: data.id, values: { config: form.values } }); ctx.setVisible(false); refresh(); }, }; }; export const NodeContext = React.createContext({}); export function useNodeContext() { return useContext(NodeContext); } export function useAvailableUpstreams(node) { const stack: any[] = []; if (!node) { return []; } for (let current = node.upstream; current; current = current.upstream) { stack.push(current); } return stack; } export function Node({ data }) { const instruction = instructions.get(data.type); return (
{instruction.render ? instruction.render(data) : } {!instruction.endding ? : (
) }
); } export function RemoveButton() { const { t } = useTranslation(); const api = useAPIClient(); const { workflow, nodes, refresh } = useFlowContext() ?? {}; const current = useNodeContext(); if (!workflow) { return null; } const resource = api.resource('workflows.nodes', workflow.id); async function onRemove() { async function onOk() { await resource.destroy?.({ filterByTk: current.id }); refresh(); } const usingNodes = nodes.filter(node => { if (node === current) { return false; } const template = parse(node.config); const refs = template.parameters.filter(({ key }) => key.startsWith(`$jobsMapByNodeId.${current.id}.`) || key === `$jobsMapByNodeId.${current.id}`); return refs.length; }); if (usingNodes.length) { Modal.error({ title: lang('Can not delete'), content: lang('The result of this node has been referenced by other nodes ({{nodes}}), please remove the usage before deleting.', { nodes: usingNodes.map(item => `#${item.id}`).join(', ') }), }); return; } const hasBranches = !nodes.find(item => item.upstream === current && item.branchIndex != null); const message = hasBranches ? t('Are you sure you want to delete it?') : lang('This node contains branches, deleting will also be preformed to them, are you sure?'); Modal.confirm({ title: t('Delete'), content: message, onOk }); } return workflow.executed ? null : (