fix(plugin-workflow): fix node form values when closed (#2978)
* fix(plugin-workflow): fix node form values when closed * fix(plugin-workflow): fix manual schema config refresh
This commit is contained in:
parent
10a1f65284
commit
2747e7640f
@ -2,7 +2,7 @@ import { css, cx } from '@emotion/css';
|
|||||||
import { useForm } from '@formily/react';
|
import { useForm } from '@formily/react';
|
||||||
import { Input } from 'antd';
|
import { Input } from 'antd';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import sanitizeHTML from 'sanitize-html';
|
import sanitizeHTML from 'sanitize-html';
|
||||||
|
|
||||||
import { error } from '@nocobase/utils/client';
|
import { error } from '@nocobase/utils/client';
|
||||||
@ -228,26 +228,30 @@ export function TextArea(props) {
|
|||||||
}
|
}
|
||||||
const sel = window.getSelection?.();
|
const sel = window.getSelection?.();
|
||||||
if (sel) {
|
if (sel) {
|
||||||
const children = Array.from(current.childNodes) as HTMLElement[];
|
try {
|
||||||
if (children.length) {
|
const children = Array.from(current.childNodes) as HTMLElement[];
|
||||||
if (range[0] === -1) {
|
if (children.length) {
|
||||||
if (range[1]) {
|
if (range[0] === -1) {
|
||||||
nextRange.setStartAfter(children[range[1] - 1]);
|
if (range[1]) {
|
||||||
|
nextRange.setStartAfter(children[range[1] - 1]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nextRange.setStart(children[range[0]], range[1]);
|
||||||
}
|
}
|
||||||
} else {
|
if (range[2] === -1) {
|
||||||
nextRange.setStart(children[range[0]], range[1]);
|
if (range[3]) {
|
||||||
}
|
nextRange.setEndAfter(children[range[3] - 1]);
|
||||||
if (range[2] === -1) {
|
}
|
||||||
if (range[3]) {
|
} else {
|
||||||
nextRange.setEndAfter(children[range[3] - 1]);
|
nextRange.setEnd(children[range[2]], range[3]);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
nextRange.setEnd(children[range[2]], range[3]);
|
|
||||||
}
|
}
|
||||||
|
nextRange.collapse(true);
|
||||||
|
sel.removeAllRanges();
|
||||||
|
sel.addRange(nextRange);
|
||||||
|
} catch (ex) {
|
||||||
|
// console.error(ex);
|
||||||
}
|
}
|
||||||
nextRange.collapse(true);
|
|
||||||
sel.removeAllRanges();
|
|
||||||
sel.addRange(nextRange);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const { lastChild } = current;
|
const { lastChild } = current;
|
||||||
@ -262,39 +266,45 @@ export function TextArea(props) {
|
|||||||
}
|
}
|
||||||
}, [html]);
|
}, [html]);
|
||||||
|
|
||||||
function onInsert(paths: string[]) {
|
const onInsert = useCallback(
|
||||||
const variable: string[] = paths.filter((key) => Boolean(key.trim()));
|
function (paths: string[]) {
|
||||||
const { current } = inputRef;
|
const variable: string[] = paths.filter((key) => Boolean(key.trim()));
|
||||||
if (!current || !variable) {
|
const { current } = inputRef;
|
||||||
return;
|
if (!current || !variable) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
current.focus();
|
current.focus();
|
||||||
|
|
||||||
const content = createVariableTagHTML(variable.join('.'), keyLabelMap);
|
const content = createVariableTagHTML(variable.join('.'), keyLabelMap);
|
||||||
pasteHTML(current, content, {
|
pasteHTML(current, content, {
|
||||||
range,
|
range,
|
||||||
});
|
});
|
||||||
|
|
||||||
setChanged(true);
|
setChanged(true);
|
||||||
setRange(getCurrentRange(current));
|
setRange(getCurrentRange(current));
|
||||||
onChange(getValue(current));
|
onChange(getValue(current));
|
||||||
}
|
},
|
||||||
|
[keyLabelMap, onChange, range],
|
||||||
|
);
|
||||||
|
|
||||||
function onInput({ currentTarget }) {
|
const onInput = useCallback(
|
||||||
if (ime) {
|
function ({ currentTarget }) {
|
||||||
return;
|
if (ime) {
|
||||||
}
|
return;
|
||||||
setChanged(true);
|
}
|
||||||
|
setChanged(true);
|
||||||
|
setRange(getCurrentRange(currentTarget));
|
||||||
|
onChange(getValue(currentTarget));
|
||||||
|
},
|
||||||
|
[ime, onChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onBlur = useCallback(function ({ currentTarget }) {
|
||||||
setRange(getCurrentRange(currentTarget));
|
setRange(getCurrentRange(currentTarget));
|
||||||
onChange(getValue(currentTarget));
|
}, []);
|
||||||
}
|
|
||||||
|
|
||||||
function onBlur({ currentTarget }) {
|
const onKeyDown = useCallback(function (ev) {
|
||||||
setRange(getCurrentRange(currentTarget));
|
|
||||||
}
|
|
||||||
|
|
||||||
function onKeyDown(ev) {
|
|
||||||
if (ev.key === 'Enter') {
|
if (ev.key === 'Enter') {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
}
|
}
|
||||||
@ -305,37 +315,40 @@ export function TextArea(props) {
|
|||||||
// if (ev.key === 'Alt') {
|
// if (ev.key === 'Alt') {
|
||||||
// console.debug(getCurrentRange(ev.currentTarget));
|
// console.debug(getCurrentRange(ev.currentTarget));
|
||||||
// }
|
// }
|
||||||
}
|
}, []);
|
||||||
|
|
||||||
function onPaste(ev) {
|
const onPaste = useCallback(
|
||||||
ev.preventDefault();
|
function (ev) {
|
||||||
const input = ev.clipboardData.getData('text/html') || ev.clipboardData.getData('text');
|
ev.preventDefault();
|
||||||
const sanitizedHTML = sanitizeHTML(input, {
|
const input = ev.clipboardData.getData('text/html') || ev.clipboardData.getData('text');
|
||||||
allowedTags: ['span'],
|
const sanitizedHTML = sanitizeHTML(input, {
|
||||||
allowedAttributes: {
|
allowedTags: ['span'],
|
||||||
span: ['data-variable', 'contenteditable'],
|
allowedAttributes: {
|
||||||
},
|
span: ['data-variable', 'contenteditable'],
|
||||||
allowedClasses: {
|
|
||||||
span: ['ant-tag', 'ant-tag-*'],
|
|
||||||
},
|
|
||||||
transformTags: {
|
|
||||||
span(tagName, attribs) {
|
|
||||||
return attribs['data-variable']
|
|
||||||
? {
|
|
||||||
tagName: tagName,
|
|
||||||
attribs,
|
|
||||||
}
|
|
||||||
: {};
|
|
||||||
},
|
},
|
||||||
},
|
allowedClasses: {
|
||||||
}).replace(/\n/g, ' ');
|
span: ['ant-tag', 'ant-tag-*'],
|
||||||
// ev.clipboardData.setData('text/html', sanitizedHTML);
|
},
|
||||||
// console.log(input, sanitizedHTML);
|
transformTags: {
|
||||||
setChanged(true);
|
span(tagName, attribs) {
|
||||||
pasteHTML(ev.currentTarget, sanitizedHTML);
|
return attribs['data-variable']
|
||||||
setRange(getCurrentRange(ev.currentTarget));
|
? {
|
||||||
onChange(getValue(ev.currentTarget));
|
tagName: tagName,
|
||||||
}
|
attribs,
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).replace(/\n/g, ' ');
|
||||||
|
// ev.clipboardData.setData('text/html', sanitizedHTML);
|
||||||
|
// console.log(input, sanitizedHTML);
|
||||||
|
setChanged(true);
|
||||||
|
pasteHTML(ev.currentTarget, sanitizedHTML);
|
||||||
|
setRange(getCurrentRange(ev.currentTarget));
|
||||||
|
onChange(getValue(ev.currentTarget));
|
||||||
|
},
|
||||||
|
[onChange],
|
||||||
|
);
|
||||||
|
|
||||||
const disabled = props.disabled || form.disabled;
|
const disabled = props.disabled || form.disabled;
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useContext, useMemo, useState } from 'react';
|
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||||
import { DeleteOutlined } from '@ant-design/icons';
|
import { DeleteOutlined } from '@ant-design/icons';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
import { createForm } from '@formily/core';
|
import { createForm } from '@formily/core';
|
||||||
@ -8,6 +8,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
ActionContextProvider,
|
ActionContextProvider,
|
||||||
|
FormProvider,
|
||||||
SchemaComponent,
|
SchemaComponent,
|
||||||
SchemaInitializerItemOptions,
|
SchemaInitializerItemOptions,
|
||||||
css,
|
css,
|
||||||
@ -273,6 +274,10 @@ export function JobButton() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function useNodeFormProps() {
|
||||||
|
return { form: useForm() };
|
||||||
|
}
|
||||||
|
|
||||||
export function NodeDefaultView(props) {
|
export function NodeDefaultView(props) {
|
||||||
const { data, children } = props;
|
const { data, children } = props;
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
@ -292,15 +297,14 @@ export function NodeDefaultView(props) {
|
|||||||
const values = cloneDeep(data.config);
|
const values = cloneDeep(data.config);
|
||||||
return createForm({
|
return createForm({
|
||||||
initialValues: values,
|
initialValues: values,
|
||||||
values,
|
|
||||||
disabled: workflow.executed,
|
disabled: workflow.executed,
|
||||||
});
|
});
|
||||||
}, [data, workflow]);
|
}, [data, workflow]);
|
||||||
|
|
||||||
const resetForm = useCallback(
|
const resetForm = useCallback(
|
||||||
(changed) => {
|
(editing) => {
|
||||||
setFormValueChanged(changed);
|
setEditingConfig(editing);
|
||||||
if (!changed) {
|
if (!editing) {
|
||||||
form.reset();
|
form.reset();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -366,123 +370,129 @@ export function NodeDefaultView(props) {
|
|||||||
<ActionContextProvider
|
<ActionContextProvider
|
||||||
value={{
|
value={{
|
||||||
visible: editingConfig,
|
visible: editingConfig,
|
||||||
setVisible: setEditingConfig,
|
setVisible: resetForm,
|
||||||
formValueChanged,
|
formValueChanged,
|
||||||
setFormValueChanged: resetForm,
|
setFormValueChanged,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SchemaComponent
|
<FormProvider form={form}>
|
||||||
scope={instruction.scope}
|
<SchemaComponent
|
||||||
components={instruction.components}
|
scope={{
|
||||||
schema={{
|
...instruction.scope,
|
||||||
type: 'void',
|
useNodeFormProps,
|
||||||
properties: {
|
}}
|
||||||
...(instruction.view ? { view: instruction.view } : {}),
|
components={instruction.components}
|
||||||
button: {
|
schema={{
|
||||||
type: 'void',
|
type: 'void',
|
||||||
'x-content': detailText,
|
properties: {
|
||||||
'x-component': Button,
|
...(instruction.view ? { view: instruction.view } : {}),
|
||||||
'x-component-props': {
|
button: {
|
||||||
type: 'link',
|
type: 'void',
|
||||||
className: 'workflow-node-config-button',
|
'x-content': detailText,
|
||||||
},
|
'x-component': Button,
|
||||||
},
|
'x-component-props': {
|
||||||
[`${instruction.type}_${data.id}`]: {
|
type: 'link',
|
||||||
type: 'void',
|
className: 'workflow-node-config-button',
|
||||||
title: (
|
|
||||||
<div
|
|
||||||
className={css`
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
strong {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-tag {
|
|
||||||
margin-inline-end: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<strong>{data.title}</strong>
|
|
||||||
<Tooltip title={lang('Variable key of node')}>
|
|
||||||
<Tag>
|
|
||||||
<code>{data.key}</code>
|
|
||||||
</Tag>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
'x-decorator': 'FormV2',
|
|
||||||
'x-decorator-props': {
|
|
||||||
form,
|
|
||||||
},
|
|
||||||
'x-component': 'Action.Drawer',
|
|
||||||
properties: {
|
|
||||||
...(instruction.description
|
|
||||||
? {
|
|
||||||
description: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': DrawerDescription,
|
|
||||||
'x-component-props': {
|
|
||||||
label: lang('Node type'),
|
|
||||||
title: instruction.title,
|
|
||||||
description: instruction.description,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
fieldset: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'fieldset',
|
|
||||||
'x-component-props': {
|
|
||||||
className: css`
|
|
||||||
.ant-input,
|
|
||||||
.ant-select,
|
|
||||||
.ant-cascader-picker,
|
|
||||||
.ant-picker,
|
|
||||||
.ant-input-number,
|
|
||||||
.ant-input-affix-wrapper {
|
|
||||||
&.auto-width {
|
|
||||||
width: auto;
|
|
||||||
min-width: 6em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
properties: instruction.fieldset,
|
|
||||||
},
|
},
|
||||||
actions: workflow.executed
|
},
|
||||||
? null
|
[`${instruction.type}_${data.id}`]: {
|
||||||
: {
|
type: 'void',
|
||||||
type: 'void',
|
title: (
|
||||||
'x-component': 'Action.Drawer.Footer',
|
<div
|
||||||
properties: {
|
className={css`
|
||||||
cancel: {
|
display: flex;
|
||||||
title: '{{t("Cancel")}}',
|
justify-content: space-between;
|
||||||
'x-component': 'Action',
|
|
||||||
|
strong {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tag {
|
||||||
|
margin-inline-end: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<strong>{data.title}</strong>
|
||||||
|
<Tooltip title={lang('Variable key of node')}>
|
||||||
|
<Tag>
|
||||||
|
<code>{data.key}</code>
|
||||||
|
</Tag>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
'x-decorator-props': {
|
||||||
|
// form,
|
||||||
|
useProps: '{{ useNodeFormProps }}',
|
||||||
|
},
|
||||||
|
'x-component': 'Action.Drawer',
|
||||||
|
properties: {
|
||||||
|
...(instruction.description
|
||||||
|
? {
|
||||||
|
description: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': DrawerDescription,
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
useAction: '{{ cm.useCancelAction }}',
|
label: lang('Node type'),
|
||||||
|
title: instruction.title,
|
||||||
|
description: instruction.description,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
submit: {
|
}
|
||||||
title: '{{t("Submit")}}',
|
: {}),
|
||||||
'x-component': 'Action',
|
fieldset: {
|
||||||
'x-component-props': {
|
type: 'void',
|
||||||
type: 'primary',
|
'x-component': 'fieldset',
|
||||||
useAction: useUpdateAction,
|
'x-component-props': {
|
||||||
|
className: css`
|
||||||
|
.ant-input,
|
||||||
|
.ant-select,
|
||||||
|
.ant-cascader-picker,
|
||||||
|
.ant-picker,
|
||||||
|
.ant-input-number,
|
||||||
|
.ant-input-affix-wrapper {
|
||||||
|
&.auto-width {
|
||||||
|
width: auto;
|
||||||
|
min-width: 6em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
properties: instruction.fieldset,
|
||||||
|
},
|
||||||
|
actions: workflow.executed
|
||||||
|
? null
|
||||||
|
: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Drawer.Footer',
|
||||||
|
properties: {
|
||||||
|
cancel: {
|
||||||
|
title: '{{t("Cancel")}}',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
useAction: '{{ cm.useCancelAction }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
submit: {
|
||||||
|
title: '{{t("Submit")}}',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'primary',
|
||||||
|
useAction: useUpdateAction,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
} as ISchema,
|
||||||
} as ISchema,
|
},
|
||||||
},
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
</FormProvider>
|
||||||
</ActionContextProvider>
|
</ActionContextProvider>
|
||||||
</div>
|
</div>
|
||||||
{children}
|
{children}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
|
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||||
import { FormLayout } from '@formily/antd-v5';
|
import { FormLayout } from '@formily/antd-v5';
|
||||||
import { createForm } from '@formily/core';
|
import { createForm } from '@formily/core';
|
||||||
import { FormProvider, ISchema, Schema, useFieldSchema, useForm } from '@formily/react';
|
import { FormProvider, ISchema, Schema, useFieldSchema, useForm } from '@formily/react';
|
||||||
import { Alert, Button, Modal, Space } from 'antd';
|
import { Alert, Button, Modal, Space } from 'antd';
|
||||||
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -450,22 +450,27 @@ export function SchemaConfig({ value, onChange }) {
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const refresh = useCallback(
|
||||||
|
function refresh() {
|
||||||
|
// ctx.refresh?.();
|
||||||
|
const { tabs } = lodash.get(schema.toJSON(), 'properties.drawer.properties') as { tabs: ISchema };
|
||||||
|
const forms = Array.from(manualFormTypes.getValues()).reduce(
|
||||||
|
(result, item: ManualFormType) => Object.assign(result, item.config.parseFormOptions(tabs)),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
form.setValuesIn('forms', forms);
|
||||||
|
|
||||||
|
onChange(tabs.properties);
|
||||||
|
},
|
||||||
|
[form, onChange, schema],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SchemaComponentContext.Provider
|
<SchemaComponentContext.Provider
|
||||||
value={{
|
value={{
|
||||||
...ctx,
|
...ctx,
|
||||||
designable: !workflow.executed,
|
designable: !workflow.executed,
|
||||||
refresh() {
|
refresh,
|
||||||
ctx.refresh?.();
|
|
||||||
const { tabs } = lodash.get(schema.toJSON(), 'properties.drawer.properties') as { tabs: ISchema };
|
|
||||||
const forms = Array.from(manualFormTypes.getValues()).reduce(
|
|
||||||
(result, item: ManualFormType) => Object.assign(result, item.config.parseFormOptions(tabs)),
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
form.setValuesIn('forms', forms);
|
|
||||||
|
|
||||||
onChange(tabs.properties);
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SchemaInitializerProvider
|
<SchemaInitializerProvider
|
||||||
@ -520,7 +525,9 @@ export function SchemaConfigButton(props) {
|
|||||||
<Button type="primary" onClick={() => setVisible(true)}>
|
<Button type="primary" onClick={() => setVisible(true)}>
|
||||||
{workflow.executed ? lang('View user interface') : lang('Configure user interface')}
|
{workflow.executed ? lang('View user interface') : lang('Configure user interface')}
|
||||||
</Button>
|
</Button>
|
||||||
<ActionContextProvider value={{ visible, setVisible }}>{props.children}</ActionContextProvider>
|
<ActionContextProvider value={{ visible, setVisible, formValueChanged: false }}>
|
||||||
|
{props.children}
|
||||||
|
</ActionContextProvider>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user