@nocobase/plugin-workflow-manual 0.20.0-alpha.9 → 0.21.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/client/index.js +4 -4
  2. package/dist/client/instruction/SchemaConfig.d.ts +14 -3
  3. package/dist/client/instruction/createManualFormBlockUISchema.d.ts +57 -0
  4. package/dist/client/instruction/forms/custom.d.ts +7 -2
  5. package/dist/externalVersion.js +9 -9
  6. package/dist/locale/index.d.ts +2 -1
  7. package/dist/locale/index.js +3 -0
  8. package/dist/locale/zh-CN.json +2 -1
  9. package/dist/server/forms/create.d.ts +2 -1
  10. package/dist/server/forms/create.js +4 -4
  11. package/dist/server/forms/update.d.ts +2 -1
  12. package/dist/server/forms/update.js +4 -4
  13. package/dist/server/migrations/20240325213145-fix-schema.d.ts +4 -0
  14. package/dist/server/migrations/20240325213145-fix-schema.js +88 -0
  15. package/package.json +2 -2
  16. package/src/client/WorkflowTodo.tsx +4 -4
  17. package/src/client/__e2e__/createRecordForm.test.ts +123 -67
  18. package/src/client/__e2e__/customFormBlocks.test.ts +135 -70
  19. package/src/client/__e2e__/datablocks.test.ts +43 -30
  20. package/src/client/__e2e__/updateRecordForm.test.ts +241 -57
  21. package/src/client/__e2e__/workflowTodo.test.ts +16 -7
  22. package/src/client/index.ts +12 -4
  23. package/src/client/instruction/FormBlockInitializer.tsx +3 -3
  24. package/src/client/instruction/SchemaConfig.tsx +163 -41
  25. package/src/client/instruction/createManualFormBlockUISchema.ts +5 -0
  26. package/src/client/instruction/forms/create.tsx +1 -1
  27. package/src/client/instruction/forms/custom.tsx +52 -21
  28. package/src/client/instruction/forms/update.tsx +16 -8
  29. package/src/client/instruction/index.tsx +1 -1
  30. package/src/locale/index.ts +3 -1
  31. package/src/locale/zh-CN.json +2 -1
  32. package/src/server/__tests__/data-source.test.ts +223 -0
  33. package/src/server/__tests__/{instruction.test.ts → form.test.ts} +1 -510
  34. package/src/server/__tests__/mode.test.ts +561 -0
  35. package/src/server/forms/create.ts +10 -3
  36. package/src/server/forms/update.ts +10 -3
  37. package/src/server/migrations/20240325213145-fix-schema.ts +81 -0
  38. package/dist/client/instruction/DetailsBlockProvider.d.ts +0 -2
  39. package/src/client/instruction/DetailsBlockProvider.tsx +0 -87
@@ -1,13 +1,14 @@
1
1
  import { faker } from '@faker-js/faker';
2
2
  import {
3
3
  CollectionTriggerNode,
4
+ ManualNode,
4
5
  apiCreateWorkflow,
5
6
  apiDeleteWorkflow,
6
7
  apiGetWorkflow,
7
8
  apiUpdateWorkflowTrigger,
8
9
  appendJsonCollectionName,
9
10
  generalWithNoRelationalFields,
10
- ManualNode,
11
+ apiGetDataSourceCount,
11
12
  } from '@nocobase/plugin-workflow-test/e2e';
12
13
  import { expect, test } from '@nocobase/test/e2e';
13
14
  import { dayjs } from '@nocobase/utils';
@@ -69,10 +70,14 @@ test('filter task node', async ({ page, mockPage, mockCollections, mockRecords }
69
70
  await manualNode.configureUserInterfaceButton.click();
70
71
  await manualNode.addBlockButton.hover();
71
72
  await manualNode.createRecordFormMenu.hover();
73
+ const dataSourcesCount = await apiGetDataSourceCount();
74
+ if (dataSourcesCount > 1) {
75
+ await page.getByRole('menuitem', { name: 'Main right' }).hover();
76
+ }
72
77
  await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click();
73
78
  await page.mouse.move(300, 0, { steps: 100 });
74
79
  await page
75
- .locator(`button[aria-label^="schema-initializer-Grid-FormItemInitializers-${manualNodeCollectionName}"]`)
80
+ .locator(`button[aria-label^="schema-initializer-Grid-form:configureFields-${manualNodeCollectionName}"]`)
76
81
  .hover();
77
82
  await page.getByLabel(`designer-schema-settings-CardItem-CreateFormDesigner-${manualNodeCollectionName}`).hover();
78
83
  await page.getByRole('menuitem', { name: 'Edit block title' }).click();
@@ -80,7 +85,7 @@ test('filter task node', async ({ page, mockPage, mockCollections, mockRecords }
80
85
  await page.getByLabel('Edit block title').getByRole('textbox').fill(blockTitle);
81
86
  await page.getByRole('button', { name: 'OK', exact: true }).click();
82
87
  await page
83
- .locator(`button[aria-label^="schema-initializer-Grid-FormItemInitializers-${manualNodeCollectionName}"]`)
88
+ .locator(`button[aria-label^="schema-initializer-Grid-form:configureFields-${manualNodeCollectionName}"]`)
84
89
  .hover();
85
90
  await page.getByRole('menuitem', { name: manualNodeFieldDisplayName }).getByRole('switch').click();
86
91
  await page.mouse.move(300, 0, { steps: 100 });
@@ -103,7 +108,7 @@ test('filter task node', async ({ page, mockPage, mockCollections, mockRecords }
103
108
  const newPage = mockPage();
104
109
  await newPage.goto();
105
110
  await page.waitForLoadState('networkidle');
106
- await page.getByLabel('schema-initializer-Grid-BlockInitializers').hover();
111
+ await page.getByLabel('schema-initializer-Grid-page:addBlock').hover();
107
112
  await page.getByRole('menuitem', { name: 'check-square Workflow todos' }).click();
108
113
  await page.mouse.move(300, 0, { steps: 100 });
109
114
  await page.waitForTimeout(300);
@@ -179,10 +184,14 @@ test('filter workflow name', async ({ page, mockPage, mockCollections, mockRecor
179
184
  await manualNode.configureUserInterfaceButton.click();
180
185
  await manualNode.addBlockButton.hover();
181
186
  await manualNode.createRecordFormMenu.hover();
187
+ const dataSourcesCount = await apiGetDataSourceCount();
188
+ if (dataSourcesCount > 1) {
189
+ await page.getByRole('menuitem', { name: 'Main right' }).hover();
190
+ }
182
191
  await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click();
183
192
  await page.mouse.move(300, 0, { steps: 100 });
184
193
  await page
185
- .locator(`button[aria-label^="schema-initializer-Grid-FormItemInitializers-${manualNodeCollectionName}"]`)
194
+ .locator(`button[aria-label^="schema-initializer-Grid-form:configureFields-${manualNodeCollectionName}"]`)
186
195
  .hover();
187
196
  await page.getByLabel(`designer-schema-settings-CardItem-CreateFormDesigner-${manualNodeCollectionName}`).hover();
188
197
  await page.getByRole('menuitem', { name: 'Edit block title' }).click();
@@ -190,7 +199,7 @@ test('filter workflow name', async ({ page, mockPage, mockCollections, mockRecor
190
199
  await page.getByLabel('Edit block title').getByRole('textbox').fill(blockTitle);
191
200
  await page.getByRole('button', { name: 'OK', exact: true }).click();
192
201
  await page
193
- .locator(`button[aria-label^="schema-initializer-Grid-FormItemInitializers-${manualNodeCollectionName}"]`)
202
+ .locator(`button[aria-label^="schema-initializer-Grid-form:configureFields-${manualNodeCollectionName}"]`)
194
203
  .hover();
195
204
  await page.getByRole('menuitem', { name: manualNodeFieldDisplayName }).getByRole('switch').click();
196
205
  await page.mouse.move(300, 0, { steps: 100 });
@@ -213,7 +222,7 @@ test('filter workflow name', async ({ page, mockPage, mockCollections, mockRecor
213
222
  const newPage = mockPage();
214
223
  await newPage.goto();
215
224
  await page.waitForLoadState('networkidle');
216
- await page.getByLabel('schema-initializer-Grid-BlockInitializers').hover();
225
+ await page.getByLabel('schema-initializer-Grid-page:addBlock').hover();
217
226
  await page.getByRole('menuitem', { name: 'check-square Workflow todos' }).click();
218
227
  await page.mouse.move(300, 0, { steps: 100 });
219
228
  await page.waitForTimeout(300);
@@ -3,11 +3,16 @@ import WorkflowPlugin from '@nocobase/plugin-workflow/client';
3
3
 
4
4
  import Manual from './instruction';
5
5
 
6
+ import { NAMESPACE } from '../locale';
6
7
  import { WorkflowTodo } from './WorkflowTodo';
7
8
  import { WorkflowTodoBlockInitializer } from './WorkflowTodoBlockInitializer';
8
- import { NAMESPACE } from '../locale';
9
- import { addActionButton, addBlockButton } from './instruction/SchemaConfig';
10
- import { addCustomFormField } from './instruction/forms/custom';
9
+ import {
10
+ addActionButton,
11
+ addActionButton_deprecated,
12
+ addBlockButton,
13
+ addBlockButton_deprecated,
14
+ } from './instruction/SchemaConfig';
15
+ import { addCustomFormField, addCustomFormField_deprecated } from './instruction/forms/custom';
11
16
 
12
17
  export default class extends Plugin {
13
18
  async afterAdd() {
@@ -22,11 +27,14 @@ export default class extends Plugin {
22
27
  const workflow = this.app.pm.get('workflow') as WorkflowPlugin;
23
28
  workflow.registerInstruction('manual', Manual);
24
29
 
30
+ this.app.schemaInitializerManager.add(addBlockButton_deprecated);
25
31
  this.app.schemaInitializerManager.add(addBlockButton);
32
+ this.app.schemaInitializerManager.add(addActionButton_deprecated);
26
33
  this.app.schemaInitializerManager.add(addActionButton);
34
+ this.app.schemaInitializerManager.add(addCustomFormField_deprecated);
27
35
  this.app.schemaInitializerManager.add(addCustomFormField);
28
36
 
29
- const blockInitializers = this.app.schemaInitializerManager.get('BlockInitializers');
37
+ const blockInitializers = this.app.schemaInitializerManager.get('page:addBlock');
30
38
  blockInitializers.add('otherBlocks.workflowTodos', {
31
39
  title: `{{t("Workflow todos", { ns: "${NAMESPACE}" })}}`,
32
40
  Component: 'WorkflowTodoBlockInitializer',
@@ -4,7 +4,6 @@ import {
4
4
  CollectionProvider_deprecated,
5
5
  SchemaInitializerItem,
6
6
  SchemaInitializerItemType,
7
- createFormBlockSchema,
8
7
  useRecordCollectionDataSourceItems,
9
8
  useSchemaInitializer,
10
9
  useSchemaInitializerItem,
@@ -14,6 +13,7 @@ import {
14
13
  import { JOB_STATUS, traverseSchema } from '@nocobase/plugin-workflow/client';
15
14
 
16
15
  import { NAMESPACE } from '../../locale';
16
+ import { createManualFormBlockUISchema } from './createManualFormBlockUISchema';
17
17
 
18
18
  function InternalFormBlockInitializer({ schema, ...others }) {
19
19
  const { getTemplateSchemaByMode } = useSchemaTemplateManager();
@@ -21,8 +21,8 @@ function InternalFormBlockInitializer({ schema, ...others }) {
21
21
  const items = useRecordCollectionDataSourceItems('FormItem') as SchemaInitializerItemType[];
22
22
  async function onConfirm({ item }) {
23
23
  const template = item.template ? await getTemplateSchemaByMode(item) : null;
24
- const result = createFormBlockSchema({
25
- actionInitializers: 'AddActionButton',
24
+ const result = createManualFormBlockUISchema({
25
+ actionInitializers: 'workflowManual:form:configureActions',
26
26
  actions: {
27
27
  resolve: {
28
28
  type: 'void',
@@ -1,30 +1,28 @@
1
1
  import { FormLayout } from '@formily/antd-v5';
2
2
  import { createForm } from '@formily/core';
3
3
  import { FormProvider, ISchema, Schema, useFieldSchema, useForm } from '@formily/react';
4
- import { Alert, Button, Modal, Space } from 'antd';
4
+ import { Alert, Button, Modal, Space, message } from 'antd';
5
5
  import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
 
8
8
  import {
9
9
  Action,
10
10
  ActionContextProvider,
11
+ CompatibleSchemaInitializer,
11
12
  DefaultValueProvider,
12
13
  FormActiveFieldsProvider,
13
14
  GeneralSchemaDesigner,
14
15
  InitializerWithSwitch,
15
16
  SchemaComponent,
16
17
  SchemaComponentContext,
17
- SchemaInitializer,
18
18
  SchemaInitializerItem,
19
19
  SchemaInitializerItemType,
20
- SchemaSettingsBlockTitleItem,
21
20
  SchemaSettingsDivider,
22
21
  SchemaSettingsItem,
23
22
  SchemaSettingsRemove,
24
23
  VariableScopeProvider,
25
24
  css,
26
25
  gridRowColWrap,
27
- useCompile,
28
26
  useDataSourceManager,
29
27
  useFormActiveFields,
30
28
  useFormBlockContext,
@@ -34,7 +32,9 @@ import {
34
32
  useSchemaOptionsContext,
35
33
  } from '@nocobase/client';
36
34
  import WorkflowPlugin, {
35
+ DetailsBlockProvider,
37
36
  JOB_STATUS,
37
+ SimpleDesigner,
38
38
  useAvailableUpstreams,
39
39
  useFlowContext,
40
40
  useNodeContext,
@@ -43,8 +43,7 @@ import WorkflowPlugin, {
43
43
  } from '@nocobase/plugin-workflow/client';
44
44
  import { Registry, lodash } from '@nocobase/utils/client';
45
45
 
46
- import { NAMESPACE, useLang } from '../../locale';
47
- import { DetailsBlockProvider } from './DetailsBlockProvider';
46
+ import { NAMESPACE, usePluginTranslation } from '../../locale';
48
47
  import { FormBlockProvider } from './FormBlockProvider';
49
48
  import createRecordForm from './forms/create';
50
49
  import customRecordForm from './forms/custom';
@@ -85,13 +84,14 @@ export type ManualFormType = {
85
84
  [key: string]: React.FC;
86
85
  };
87
86
  };
87
+ validate?: (config: any) => string | null;
88
88
  };
89
89
 
90
90
  export const manualFormTypes = new Registry<ManualFormType>();
91
91
 
92
- manualFormTypes.register('customForm', customRecordForm);
93
- manualFormTypes.register('createForm', createRecordForm);
94
- manualFormTypes.register('updateForm', updateRecordForm);
92
+ manualFormTypes.register('custom', customRecordForm);
93
+ manualFormTypes.register('create', createRecordForm);
94
+ manualFormTypes.register('update', updateRecordForm);
95
95
 
96
96
  function useTriggerInitializers(): SchemaInitializerItemType | null {
97
97
  const { workflow } = useFlowContext();
@@ -104,25 +104,11 @@ const blockTypeNames = {
104
104
  record: `{{t("Data record", { ns: "${NAMESPACE}" })}}`,
105
105
  };
106
106
 
107
- function SimpleDesigner() {
108
- const schema = useFieldSchema();
109
- const title = blockTypeNames[schema['x-designer-props']?.type] ?? '{{t("Block")}}';
110
- const compile = useCompile();
111
- return (
112
- <GeneralSchemaDesigner title={compile(title)}>
113
- <SchemaSettingsBlockTitleItem />
114
- <SchemaSettingsDivider />
115
- <SchemaSettingsRemove
116
- removeParentsIfNoChildren
117
- breakRemoveOn={{
118
- 'x-component': 'Grid',
119
- }}
120
- />
121
- </GeneralSchemaDesigner>
122
- );
123
- }
124
-
125
- export const addBlockButton: SchemaInitializer = new SchemaInitializer({
107
+ /**
108
+ * @deprecated
109
+ * use `addBlockButton` instead
110
+ */
111
+ export const addBlockButton_deprecated = new CompatibleSchemaInitializer({
126
112
  name: 'AddBlockButton',
127
113
  wrap: gridRowColWrap,
128
114
  title: '{{t("Add block")}}',
@@ -150,7 +136,7 @@ export const addBlockButton: SchemaInitializer = new SchemaInitializer({
150
136
  {
151
137
  name: 'nodes',
152
138
  type: 'subMenu',
153
- title: `{{t("Node result", { ns: "${NAMESPACE}" })}}`,
139
+ title: `{{t("Node result", { ns: "workflow" })}}`,
154
140
  children: nodeBlockInitializers,
155
141
  },
156
142
  ]
@@ -187,9 +173,78 @@ export const addBlockButton: SchemaInitializer = new SchemaInitializer({
187
173
  ],
188
174
  });
189
175
 
176
+ export const addBlockButton = new CompatibleSchemaInitializer(
177
+ {
178
+ name: 'workflowManual:popup:configureUserInterface:addBlock',
179
+ wrap: gridRowColWrap,
180
+ title: '{{t("Add block")}}',
181
+ items: [
182
+ {
183
+ type: 'itemGroup',
184
+ name: 'dataBlocks',
185
+ title: '{{t("Data blocks")}}',
186
+ hideIfNoChildren: true,
187
+ useChildren() {
188
+ const workflowPlugin = usePlugin(WorkflowPlugin);
189
+ const current = useNodeContext();
190
+ const nodes = useAvailableUpstreams(current);
191
+ const triggerInitializers = [useTriggerInitializers()].filter(Boolean);
192
+ const nodeBlockInitializers = nodes
193
+ .map((node) => {
194
+ const instruction = workflowPlugin.instructions.get(node.type);
195
+ return instruction?.useInitializers?.(node);
196
+ })
197
+ .filter(Boolean);
198
+ const dataBlockInitializers: any = [
199
+ ...triggerInitializers,
200
+ ...(nodeBlockInitializers.length
201
+ ? [
202
+ {
203
+ name: 'nodes',
204
+ type: 'subMenu',
205
+ title: `{{t("Node result", { ns: "${NAMESPACE}" })}}`,
206
+ children: nodeBlockInitializers,
207
+ },
208
+ ]
209
+ : []),
210
+ ].filter(Boolean);
211
+ return dataBlockInitializers;
212
+ },
213
+ },
214
+ {
215
+ type: 'itemGroup',
216
+ name: 'form',
217
+ title: '{{t("Form")}}',
218
+ useChildren() {
219
+ const dm = useDataSourceManager();
220
+ const allCollections = dm.getAllCollections();
221
+ return Array.from(manualFormTypes.getValues()).map((item: ManualFormType) => {
222
+ const { useInitializer: getInitializer } = item.config;
223
+ return getInitializer({ allCollections });
224
+ });
225
+ },
226
+ },
227
+ {
228
+ type: 'itemGroup',
229
+ name: 'otherBlocks',
230
+ title: '{{t("Other blocks")}}',
231
+ children: [
232
+ {
233
+ name: 'markdown',
234
+ title: '{{t("Markdown")}}',
235
+ Component: 'MarkdownBlockInitializer',
236
+ },
237
+ ],
238
+ },
239
+ ],
240
+ },
241
+ addBlockButton_deprecated,
242
+ );
243
+
190
244
  function AssignedFieldValues() {
191
245
  const ctx = useContext(SchemaComponentContext);
192
- const { t } = useTranslation();
246
+ const { t: coreT } = useTranslation();
247
+ const { t } = usePluginTranslation();
193
248
  const fieldSchema = useFieldSchema();
194
249
  const scope = useWorkflowVariableOptions();
195
250
  const [open, setOpen] = useState(false);
@@ -197,7 +252,7 @@ function AssignedFieldValues() {
197
252
  fieldSchema?.['x-action-settings']?.assignedValues?.schema ?? {
198
253
  type: 'void',
199
254
  'x-component': 'Grid',
200
- 'x-initializer': 'CustomFormItemInitializers',
255
+ 'x-initializer': 'assignFieldValuesForm:configureFields',
201
256
  properties: {},
202
257
  },
203
258
  );
@@ -221,7 +276,7 @@ function AssignedFieldValues() {
221
276
  }, [fieldSchema]);
222
277
  const upLevelActiveFields = useFormActiveFields();
223
278
 
224
- const title = t('Assign field values');
279
+ const title = coreT('Assign field values');
225
280
 
226
281
  function onCancel() {
227
282
  setOpen(false);
@@ -267,9 +322,7 @@ function AssignedFieldValues() {
267
322
  <FormProvider form={form}>
268
323
  <FormLayout layout={'vertical'}>
269
324
  <Alert
270
- message={useLang(
271
- 'Values preset in this form will override user submitted ones when continue or reject.',
272
- )}
325
+ message={t('Values preset in this form will override user submitted ones when continue or reject.')}
273
326
  />
274
327
  <br />
275
328
  {open && schema && (
@@ -365,7 +418,11 @@ function ActionInitializer() {
365
418
  );
366
419
  }
367
420
 
368
- export const addActionButton: SchemaInitializer = new SchemaInitializer({
421
+ /**
422
+ * @deprecated
423
+ * use `addActionButton` instead
424
+ */
425
+ export const addActionButton_deprecated = new CompatibleSchemaInitializer({
369
426
  name: 'AddActionButton',
370
427
  title: '{{t("Configure actions")}}',
371
428
  items: [
@@ -396,6 +453,40 @@ export const addActionButton: SchemaInitializer = new SchemaInitializer({
396
453
  ],
397
454
  });
398
455
 
456
+ export const addActionButton = new CompatibleSchemaInitializer(
457
+ {
458
+ name: 'workflowManual:form:configureActions',
459
+ title: '{{t("Configure actions")}}',
460
+ items: [
461
+ {
462
+ name: 'jobStatusResolved',
463
+ title: `{{t("Continue the process", { ns: "${NAMESPACE}" })}}`,
464
+ Component: ContinueInitializer,
465
+ action: JOB_STATUS.RESOLVED,
466
+ actionProps: {
467
+ type: 'primary',
468
+ },
469
+ },
470
+ {
471
+ name: 'jobStatusRejected',
472
+ title: `{{t("Terminate the process", { ns: "${NAMESPACE}" })}}`,
473
+ Component: ActionInitializer,
474
+ action: JOB_STATUS.REJECTED,
475
+ actionProps: {
476
+ danger: true,
477
+ },
478
+ },
479
+ {
480
+ name: 'jobStatusPending',
481
+ title: `{{t("Save temporarily", { ns: "${NAMESPACE}" })}}`,
482
+ Component: ActionInitializer,
483
+ action: JOB_STATUS.PENDING,
484
+ },
485
+ ],
486
+ },
487
+ addActionButton_deprecated,
488
+ );
489
+
399
490
  // NOTE: fake useAction for ui configuration
400
491
  function useSubmit() {
401
492
  // const { values, submit, id: formId } = useForm();
@@ -440,9 +531,9 @@ export function SchemaConfig({ value, onChange }) {
440
531
  type: 'void',
441
532
  'x-component': 'Tabs',
442
533
  'x-component-props': {},
443
- 'x-initializer': 'TabPaneInitializers',
534
+ 'x-initializer': 'popup:addTab',
444
535
  'x-initializer-props': {
445
- gridInitializer: 'AddBlockButton',
536
+ gridInitializer: 'workflowManual:popup:configureUserInterface:addBlock',
446
537
  },
447
538
  properties: value ?? {
448
539
  tab1: {
@@ -454,7 +545,7 @@ export function SchemaConfig({ value, onChange }) {
454
545
  grid: {
455
546
  type: 'void',
456
547
  'x-component': 'Grid',
457
- 'x-initializer': 'AddBlockButton',
548
+ 'x-initializer': 'workflowManual:popup:configureUserInterface:addBlock',
458
549
  properties: {},
459
550
  },
460
551
  },
@@ -521,15 +612,46 @@ export function SchemaConfig({ value, onChange }) {
521
612
  );
522
613
  }
523
614
 
615
+ function validateForms(forms: Record<string, any> = {}) {
616
+ for (const form of Object.values(forms)) {
617
+ const formType = manualFormTypes.get(form.type);
618
+ if (typeof formType.validate === 'function') {
619
+ const msg = formType.validate(form);
620
+ if (msg) {
621
+ return msg;
622
+ }
623
+ }
624
+ }
625
+ }
626
+
524
627
  export function SchemaConfigButton(props) {
525
628
  const { workflow } = useFlowContext();
526
629
  const [visible, setVisible] = useState(false);
630
+ const { values } = useForm();
631
+ const { t } = usePluginTranslation();
632
+ const onSetVisible = useCallback(
633
+ (v) => {
634
+ if (!v) {
635
+ const msg = validateForms(values.forms);
636
+ if (msg) {
637
+ message.error({
638
+ // eslint-disable-next-line react-hooks/rules-of-hooks
639
+ title: t('Validation failed'),
640
+ content: t(msg),
641
+ });
642
+ return;
643
+ }
644
+ }
645
+ setVisible(v);
646
+ },
647
+ [values.forms],
648
+ );
527
649
  return (
528
650
  <>
529
651
  <Button type="primary" onClick={() => setVisible(true)} disabled={false}>
530
- {useLang(workflow.executed ? 'View user interface' : 'Configure user interface')}
652
+ {t(workflow.executed ? 'View user interface' : 'Configure user interface')}
531
653
  </Button>
532
- <ActionContextProvider value={{ visible, setVisible, formValueChanged: false }}>
654
+ <ActionContextProvider value={{ visible, setVisible: onSetVisible, formValueChanged: false }}>
533
655
  {props.children}
534
656
  </ActionContextProvider>
535
657
  </>
@@ -0,0 +1,5 @@
1
+ import { createFormBlockSchema } from '@nocobase/client';
2
+
3
+ export function createManualFormBlockUISchema(options) {
4
+ return createFormBlockSchema(options);
5
+ }
@@ -65,7 +65,7 @@ export default {
65
65
  [allCollections],
66
66
  );
67
67
  const [openMenuKeys, setOpenMenuKeys] = useState([]);
68
- const searchedChildren = useMenuSearch(childItems, openMenuKeys);
68
+ const searchedChildren = useMenuSearch({ data: childItems, openKeys: openMenuKeys });
69
69
 
70
70
  return {
71
71
  name: 'createRecordForm',
@@ -3,15 +3,15 @@ import React, { useContext, useMemo, useState } from 'react';
3
3
  import { ArrayTable } from '@formily/antd-v5';
4
4
  import { Field, createForm } from '@formily/core';
5
5
  import { useField, useFieldSchema, useForm } from '@formily/react';
6
- import lodash from 'lodash';
6
+ import { cloneDeep, pick, set } from 'lodash';
7
7
 
8
8
  import {
9
9
  ActionContextProvider,
10
10
  CollectionProvider_deprecated,
11
+ CompatibleSchemaInitializer,
11
12
  FormBlockContext,
12
13
  RecordProvider,
13
14
  SchemaComponent,
14
- SchemaInitializer,
15
15
  SchemaInitializerItem,
16
16
  SchemaInitializerItemType,
17
17
  SchemaInitializerItems,
@@ -95,15 +95,12 @@ function CustomFormBlockInitializer() {
95
95
  [uid()]: {
96
96
  type: 'void',
97
97
  'x-component': 'FormV2',
98
- 'x-component-props': {
99
- // disabled / read-pretty / initialValues
100
- useProps: '{{ useFormBlockProps }}',
101
- },
98
+ 'x-use-component-props': 'useFormBlockProps',
102
99
  properties: {
103
100
  grid: {
104
101
  type: 'void',
105
102
  'x-component': 'Grid',
106
- 'x-initializer': 'AddCustomFormField',
103
+ 'x-initializer': 'workflowManual:customForm:configureFields',
107
104
  },
108
105
  actions: {
109
106
  type: 'void',
@@ -116,7 +113,7 @@ function CustomFormBlockInitializer() {
116
113
  flexWrap: 'wrap',
117
114
  },
118
115
  },
119
- 'x-initializer': 'AddActionButton',
116
+ 'x-initializer': 'workflowManual:form:configureActions',
120
117
  properties: {
121
118
  resolve: {
122
119
  type: 'void',
@@ -161,7 +158,7 @@ function getOptions(interfaces) {
161
158
  const schema = interfaces[type];
162
159
  const { group = 'others' } = schema;
163
160
  fields[group] = fields[group] || {};
164
- lodash.set(fields, [group, type], schema);
161
+ set(fields, [group, type], schema);
165
162
  });
166
163
 
167
164
  return Object.keys(GroupLabels)
@@ -208,33 +205,51 @@ const CustomItemsComponent = (props) => {
208
205
  const items = useCommonInterfaceInitializers();
209
206
  const collection = useCollection_deprecated();
210
207
  const { setCollectionFields } = useContext(FormBlockContext);
208
+ const form = useMemo(() => createForm(), [interfaceOptions]);
211
209
 
212
210
  return (
213
211
  <AddCustomFormFieldButtonContext.Provider
214
212
  value={{
215
213
  onAddField(item) {
214
+ const fieldInterface: Record<string, any> = pick(item, [
215
+ 'name',
216
+ 'group',
217
+ 'title',
218
+ 'default',
219
+ 'validateSchema',
220
+ ]);
216
221
  const {
217
- properties: { unique, type, ...properties },
218
- ...options
219
- } = lodash.cloneDeep(item);
220
- delete properties.name['x-disabled'];
221
- setInterface({
222
- ...options,
223
- properties,
224
- });
222
+ properties: { unique, type, layout, autoIncrement, ...properties },
223
+ } = item;
224
+ fieldInterface.properties = properties;
225
+ const result = cloneDeep(fieldInterface);
226
+ delete result.properties.name['x-disabled'];
227
+ setInterface(result);
225
228
  },
226
229
  setCallback,
227
230
  }}
228
231
  >
229
232
  <SchemaInitializerItems {...props} items={items} />
230
- <ActionContextProvider value={{ visible: Boolean(interfaceOptions) }}>
233
+ <ActionContextProvider
234
+ value={{
235
+ visible: Boolean(interfaceOptions),
236
+ setVisible(v) {
237
+ if (!v) {
238
+ setInterface(null);
239
+ }
240
+ },
241
+ }}
242
+ >
231
243
  {interfaceOptions ? (
232
244
  <SchemaComponent
233
245
  schema={{
234
246
  type: 'void',
235
247
  name: 'drawer',
236
248
  title: '{{t("Configure field")}}',
237
- 'x-decorator': 'Form',
249
+ 'x-decorator': 'FormV2',
250
+ 'x-decorator-props': {
251
+ form,
252
+ },
238
253
  'x-component': 'Action.Drawer',
239
254
  properties: {
240
255
  ...interfaceOptions.properties,
@@ -266,7 +281,7 @@ const CustomItemsComponent = (props) => {
266
281
  'x-component-props': {
267
282
  type: 'primary',
268
283
  useAction() {
269
- const { values, query } = useForm();
284
+ const { values, query, reset } = useForm();
270
285
  const messages = [useLang('Field name existed in form')];
271
286
  return {
272
287
  async run() {
@@ -301,6 +316,7 @@ const CustomItemsComponent = (props) => {
301
316
  'x-toolbar': 'FormItemSchemaToolbar',
302
317
  'x-settings': 'fieldSettings:FormItem',
303
318
  });
319
+ reset();
304
320
  setCallback(null);
305
321
  setInterface(null);
306
322
  },
@@ -322,7 +338,11 @@ const CustomItemsComponent = (props) => {
322
338
  );
323
339
  };
324
340
 
325
- export const addCustomFormField: SchemaInitializer = new SchemaInitializer({
341
+ /**
342
+ * @deprecated
343
+ * use `addCustomFormField` instead
344
+ */
345
+ export const addCustomFormField_deprecated = new CompatibleSchemaInitializer({
326
346
  name: 'AddCustomFormField',
327
347
  wrap: gridRowColWrap,
328
348
  insertPosition: 'beforeEnd',
@@ -330,6 +350,17 @@ export const addCustomFormField: SchemaInitializer = new SchemaInitializer({
330
350
  ItemsComponent: CustomItemsComponent,
331
351
  });
332
352
 
353
+ export const addCustomFormField = new CompatibleSchemaInitializer(
354
+ {
355
+ name: 'workflowManual:customForm:configureFields',
356
+ wrap: gridRowColWrap,
357
+ insertPosition: 'beforeEnd',
358
+ title: "{{t('Configure fields')}}",
359
+ ItemsComponent: CustomItemsComponent,
360
+ },
361
+ addCustomFormField_deprecated,
362
+ );
363
+
333
364
  function CustomFormFieldInitializer() {
334
365
  const itemConfig = useSchemaInitializerItem();
335
366
  const { insert, setVisible } = useSchemaInitializer();