mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-08 15:09:27 +08:00
371 lines
10 KiB
TypeScript
371 lines
10 KiB
TypeScript
import React, { useState, useContext } from 'react';
|
|
|
|
import { useForm, ISchema, Schema, useFieldSchema } from '@formily/react';
|
|
import { get } from 'lodash';
|
|
|
|
import {
|
|
SchemaComponent,
|
|
SchemaComponentContext,
|
|
SchemaInitializer,
|
|
SchemaInitializerItemOptions,
|
|
InitializerWithSwitch,
|
|
SchemaInitializerProvider,
|
|
useSchemaComponentContext,
|
|
gridRowColWrap,
|
|
ActionContext,
|
|
GeneralSchemaDesigner,
|
|
SchemaSettings,
|
|
useCompile,
|
|
} from '@nocobase/client';
|
|
import { useTrigger } from '../../triggers';
|
|
import { instructions, useAvailableUpstreams, useNodeContext } from '..';
|
|
import { useFlowContext } from '../../FlowContext';
|
|
import { lang, NAMESPACE } from '../../locale';
|
|
import { JOB_STATUS } from '../../constants';
|
|
import customForm from './forms/customForm';
|
|
|
|
function useTriggerInitializers(): SchemaInitializerItemOptions | null {
|
|
const { workflow } = useFlowContext();
|
|
const trigger = useTrigger();
|
|
return trigger.useInitializers ? trigger.useInitializers(workflow.config) : null;
|
|
}
|
|
|
|
const blockTypeNames = {
|
|
customForm: customForm.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 (
|
|
<GeneralSchemaDesigner title={compile(title)}>
|
|
<SchemaSettings.BlockTitleItem />
|
|
<SchemaSettings.Divider />
|
|
<SchemaSettings.Remove
|
|
removeParentsIfNoChildren
|
|
breakRemoveOn={{
|
|
'x-component': 'Grid',
|
|
}}
|
|
/>
|
|
</GeneralSchemaDesigner>
|
|
);
|
|
}
|
|
|
|
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: [
|
|
customForm.config.initializer,
|
|
// {
|
|
// key: 'createForm',
|
|
// type: 'item',
|
|
// title: '{{t("Create record form")}}',
|
|
// component: CustomFormBlockInitializer,
|
|
// },
|
|
// {
|
|
// key: 'updateForm',
|
|
// type: 'item',
|
|
// title: '{{t("Update record form")}}',
|
|
// component: CustomFormBlockInitializer,
|
|
// }
|
|
],
|
|
},
|
|
{
|
|
type: 'itemGroup',
|
|
title: '{{t("Other blocks")}}',
|
|
children: [
|
|
{
|
|
type: 'item',
|
|
title: '{{t("Markdown")}}',
|
|
component: 'MarkdownBlockInitializer',
|
|
},
|
|
],
|
|
},
|
|
] as SchemaInitializerItemOptions[];
|
|
|
|
return <SchemaInitializer.Button {...props} wrap={gridRowColWrap} items={items} title="{{t('Add block')}}" />;
|
|
}
|
|
|
|
function findSchema(schema, filter, onlyLeaf = false) {
|
|
const result = [];
|
|
|
|
if (!schema) {
|
|
return result;
|
|
}
|
|
|
|
if (filter(schema) && (!onlyLeaf || !schema.properties)) {
|
|
result.push(schema);
|
|
return result;
|
|
}
|
|
|
|
if (schema.properties) {
|
|
Object.keys(schema.properties).forEach((key) => {
|
|
result.push(...findSchema(schema.properties[key], filter));
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function SchemaComponentRefreshProvider(props) {
|
|
const ctx = useSchemaComponentContext();
|
|
return (
|
|
<SchemaComponentContext.Provider
|
|
value={{
|
|
...ctx,
|
|
refresh() {
|
|
ctx?.refresh?.();
|
|
props?.onRefresh?.();
|
|
},
|
|
}}
|
|
>
|
|
{props.children}
|
|
</SchemaComponentContext.Provider>
|
|
);
|
|
}
|
|
|
|
function ActionInitializer({ action, actionProps, ...props }) {
|
|
return (
|
|
<InitializerWithSwitch
|
|
{...props}
|
|
schema={{
|
|
type: 'void',
|
|
title: props.title,
|
|
'x-decorator': 'ManualActionStatusProvider',
|
|
'x-decorator-props': {
|
|
value: action,
|
|
},
|
|
'x-component': 'Action',
|
|
'x-component-props': {
|
|
...actionProps,
|
|
useAction: '{{ useSubmit }}',
|
|
},
|
|
'x-designer': 'Action.Designer',
|
|
'x-action': `${action}`,
|
|
}}
|
|
type="x-action"
|
|
/>
|
|
);
|
|
}
|
|
|
|
function AddActionButton(props) {
|
|
return (
|
|
<SchemaInitializer.Button
|
|
{...props}
|
|
items={[
|
|
{
|
|
key: JOB_STATUS.RESOLVED,
|
|
type: 'item',
|
|
title: `{{t("Continue the process", { ns: "${NAMESPACE}" })}}`,
|
|
component: ActionInitializer,
|
|
action: JOB_STATUS.RESOLVED,
|
|
actionProps: {
|
|
type: 'primary',
|
|
},
|
|
},
|
|
{
|
|
key: JOB_STATUS.REJECTED,
|
|
type: 'item',
|
|
title: `{{t("Terminate the process", { ns: "${NAMESPACE}" })}}`,
|
|
component: ActionInitializer,
|
|
action: JOB_STATUS.REJECTED,
|
|
actionProps: {
|
|
type: 'danger',
|
|
},
|
|
},
|
|
{
|
|
key: JOB_STATUS.PENDING,
|
|
type: 'item',
|
|
title: `{{t("Save temporarily", { ns: "${NAMESPACE}" })}}`,
|
|
component: ActionInitializer,
|
|
action: JOB_STATUS.PENDING,
|
|
},
|
|
]}
|
|
title="{{t('Configure actions')}}"
|
|
/>
|
|
);
|
|
}
|
|
|
|
// NOTE: fake useAction for ui configuration
|
|
function useSubmit() {
|
|
// const { values, submit, id: formId } = useForm();
|
|
// const formSchema = useFieldSchema();
|
|
return {
|
|
run() {},
|
|
};
|
|
}
|
|
|
|
function useFlowRecordFromBlock() {
|
|
return {};
|
|
}
|
|
|
|
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 = 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 (
|
|
<SchemaComponentContext.Provider value={{ ...ctx, designable: !workflow.executed }}>
|
|
<SchemaInitializerProvider
|
|
initializers={{
|
|
AddBlockButton,
|
|
AddActionButton,
|
|
...trigger.initializers,
|
|
...nodeInitializers,
|
|
...customForm.config.initializers,
|
|
}}
|
|
>
|
|
<SchemaComponentRefreshProvider
|
|
onRefresh={() => {
|
|
const forms = {};
|
|
const { tabs } = get(schema.toJSON(), 'properties.drawer.properties') as { tabs: ISchema };
|
|
const formBlocks: any[] = findSchema(tabs, (item) => item['x-decorator'] === 'FormCollectionProvider');
|
|
formBlocks.forEach((formBlock) => {
|
|
const [formKey] = Object.keys(formBlock.properties);
|
|
const formSchema = formBlock.properties[formKey];
|
|
const fields = findSchema(
|
|
formSchema.properties.grid,
|
|
(item) => item['x-component'] === 'CollectionField',
|
|
true,
|
|
);
|
|
formBlock['x-decorator-props'].collection.fields = fields.map((field) => field['x-interface-options']);
|
|
forms[formKey] = {
|
|
type: 'custom',
|
|
title: formBlock['x-component-props']?.title || formKey,
|
|
actions: findSchema(formSchema.properties.actions, (item) => item['x-component'] === 'Action').map(
|
|
(item) => item['x-decorator-props'].value,
|
|
),
|
|
collection: formBlock['x-decorator-props'].collection,
|
|
};
|
|
});
|
|
|
|
form.setValuesIn('forms', forms);
|
|
|
|
onChange(tabs.properties);
|
|
}}
|
|
>
|
|
<SchemaComponent
|
|
schema={schema}
|
|
components={{
|
|
...nodeComponents,
|
|
...customForm.config.components,
|
|
// NOTE: fake provider component
|
|
ManualActionStatusProvider(props) {
|
|
return props.children;
|
|
},
|
|
ActionBarProvider(props) {
|
|
return props.children;
|
|
},
|
|
SimpleDesigner,
|
|
}}
|
|
scope={{
|
|
useSubmit,
|
|
useFlowRecordFromBlock,
|
|
}}
|
|
/>
|
|
</SchemaComponentRefreshProvider>
|
|
</SchemaInitializerProvider>
|
|
</SchemaComponentContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function SchemaConfigButton(props) {
|
|
const { workflow } = useFlowContext();
|
|
const [visible, setVisible] = useState(false);
|
|
return (
|
|
<>
|
|
<div className="ant-btn ant-btn-primary" onClick={() => setVisible(true)}>
|
|
{workflow.executed ? lang('View user interface') : lang('Configure user interface')}
|
|
</div>
|
|
<ActionContext.Provider value={{ visible, setVisible }}>{props.children}</ActionContext.Provider>
|
|
</>
|
|
);
|
|
}
|