feat: new action.area (#1113)

Reviewed-on: daoyoucloud/tachybase#1113
This commit is contained in:
sealday 2024-06-04 16:51:57 +08:00
parent 5c25f23082
commit b2970245d9
15 changed files with 277 additions and 155 deletions

View File

@ -0,0 +1,8 @@
import { createStyles } from 'antd-style';
export const useStyles = createStyles(({ css, token }) => {
return css`
padding-top: ${token.paddingContentVerticalLG}px;
background-color: var(--colorBgDrawer);
`;
});

View File

@ -0,0 +1,86 @@
import React, { useContext, useEffect, useRef, useState } from 'react';
import { observer, RecursionField, useField, useFieldSchema } from '@tachybase/schema';
import { createPortal } from 'react-dom';
import { useStyles } from './Action.Area.style';
import { useActionContext } from './hooks';
import { ComposedActionDrawer } from './types';
const ActionAreaContext = React.createContext<any>({});
export const ActionAreaProvider = ({ children }: { children: React.ReactNode }) => {
const [component, setComponent] = useState(() => {});
const [prevSetVisible, setPrevSetVisible] = useState(() => {});
const ref = useRef();
return (
<ActionAreaContext.Provider value={{ component, setComponent, prevSetVisible, setPrevSetVisible, ref }}>
{children}
</ActionAreaContext.Provider>
);
};
export const useActionAreaStub = () => {
const { component } = useContext(ActionAreaContext);
return component;
};
export const ActionAreaStub = () => {
const { ref } = useContext(ActionAreaContext);
return <div ref={ref}></div>;
};
export const ActionAreaPlayer = ({ children }) => {
const { visible, setVisible } = useActionContext();
const { prevSetVisible, setPrevSetVisible, ref } = useContext(ActionAreaContext);
useEffect(() => {
if (visible) {
setPrevSetVisible(() => {
prevSetVisible?.(false);
return setVisible;
});
}
}, [visible]);
return visible ? createPortal(children, ref.current) : null;
};
export const ActionArea: ComposedActionDrawer = observer(
(props) => {
const { footerNodeName = 'Action.Drawer.Footer' } = props;
const schema = useFieldSchema();
const field = useField();
const { styles } = useStyles();
const Current = () => (
<div className={styles}>
<RecursionField
basePath={field.address}
schema={schema}
onlyRenderProperties
filterProperties={(s) => {
return s['x-component'] !== footerNodeName;
}}
/>
</div>
);
return (
<ActionAreaPlayer>
<Current />
</ActionAreaPlayer>
);
},
{ displayName: 'ActionArea' },
);
ActionArea.Footer = observer(
() => {
const field = useField();
const schema = useFieldSchema();
return <RecursionField basePath={field.address} schema={schema} onlyRenderProperties />;
},
{ displayName: 'ActionArea.Footer' },
);
export default ActionArea;

View File

@ -17,6 +17,7 @@ import { useLocalVariables, useVariables } from '../../../variables';
import { SortableItem } from '../../common'; import { SortableItem } from '../../common';
import { useCompile, useComponent, useDesigner } from '../../hooks'; import { useCompile, useComponent, useDesigner } from '../../hooks';
import { useProps } from '../../hooks/useProps'; import { useProps } from '../../hooks/useProps';
import { ActionArea } from './Action.Area';
import ActionContainer from './Action.Container'; import ActionContainer from './Action.Container';
import { ActionDesigner } from './Action.Designer'; import { ActionDesigner } from './Action.Designer';
import { ActionDrawer } from './Action.Drawer'; import { ActionDrawer } from './Action.Drawer';
@ -231,5 +232,6 @@ Action.Drawer = ActionDrawer;
Action.Modal = ActionModal; Action.Modal = ActionModal;
Action.Container = ActionContainer; Action.Container = ActionContainer;
Action.Page = ActionPage; Action.Page = ActionPage;
Action.Area = ActionArea;
export default Action; export default Action;

View File

@ -7,3 +7,4 @@ export * from './hooks/useGetAriaLabelOfDrawer';
export * from './hooks/useGetAriaLabelOfModal'; export * from './hooks/useGetAriaLabelOfModal';
export * from './hooks/useGetAriaLabelOfPopover'; export * from './hooks/useGetAriaLabelOfPopover';
export * from './Action.Designer'; export * from './Action.Designer';
export * from './Action.Area';

View File

@ -1,7 +1,7 @@
import { Alert, Slider } from 'antd';
import React from 'react'; import React from 'react';
import { css, cx } from '@tachybase/client';
import { cx, css } from '@tachybase/client'; import { Alert, Slider } from 'antd';
import { Branch } from './Branch'; import { Branch } from './Branch';
import { useFlowContext } from './FlowContext'; import { useFlowContext } from './FlowContext';

View File

@ -1,28 +1,31 @@
import { DownOutlined, EllipsisOutlined, RightOutlined } from '@ant-design/icons'; import React, { useEffect, useState } from 'react';
import { import {
ActionAreaProvider,
ActionAreaStub,
ActionContextProvider, ActionContextProvider,
cx,
ResourceActionProvider, ResourceActionProvider,
SchemaComponent, SchemaComponent,
cx,
useApp, useApp,
useDocumentTitle, useDocumentTitle,
useResourceActionContext, useResourceActionContext,
useResourceContext, useResourceContext,
} from '@tachybase/client'; } from '@tachybase/client';
import { str2moment } from '@tachybase/utils/client'; import { str2moment } from '@tachybase/utils/client';
import { App, Breadcrumb, Button, Dropdown, Result, Spin, Switch, message } from 'antd';
import React, { useEffect, useState } from 'react'; import { DownOutlined, EllipsisOutlined, RightOutlined } from '@ant-design/icons';
import { App, Breadcrumb, Button, Dropdown, message, Result, Spin, Switch } from 'antd';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { CanvasContent } from './CanvasContent'; import { CanvasContent } from './CanvasContent';
import { ExecutionStatusColumn } from './components/ExecutionStatus';
import { ExecutionLink } from './ExecutionLink'; import { ExecutionLink } from './ExecutionLink';
import { FlowContext, useFlowContext } from './FlowContext'; import { FlowContext, useFlowContext } from './FlowContext';
import { lang } from './locale'; import { lang } from './locale';
import { executionSchema } from './schemas/executions'; import { executionSchema } from './schemas/executions';
import useStyles from './style'; import useStyles from './style';
import { linkNodes, getWorkflowDetailPath } from './utils'; import { getWorkflowDetailPath, linkNodes } from './utils';
import { ExecutionStatusColumn } from './components/ExecutionStatus';
function ExecutionResourceProvider({ request, filter = {}, ...others }) { function ExecutionResourceProvider({ request, filter = {}, ...others }) {
const { workflow } = useFlowContext(); const { workflow } = useFlowContext();
@ -236,7 +239,14 @@ export function WorkflowCanvas() {
</ActionContextProvider> </ActionContextProvider>
</aside> </aside>
</div> </div>
<CanvasContent entry={entry} /> <ActionAreaProvider>
<div className="workflow-content">
<CanvasContent entry={entry} />
<div className="workflow-operator-area">
<ActionAreaStub />
</div>
</div>
</ActionAreaProvider>
</FlowContext.Provider> </FlowContext.Provider>
); );
} }

View File

@ -1,15 +1,16 @@
import { SchemaComponent, SchemaComponentContext, usePlugin, useRecord } from '@tachybase/client';
import { Card } from 'antd';
import React, { useContext, useEffect } from 'react'; import React, { useContext, useEffect } from 'react';
import { SchemaComponent, SchemaComponentContext, usePlugin, useRecord } from '@tachybase/client';
import { onFieldChange, useField, useFormEffects } from '@tachybase/schema';
import { Card } from 'antd';
import WorkflowPlugin, { RadioWithTooltip } from '.';
import { ExecutionStatusColumn, ExecutionStatusSelect } from './components/ExecutionStatus';
import OpenDrawer from './components/OpenDrawer';
import { ExecutionLink } from './ExecutionLink'; import { ExecutionLink } from './ExecutionLink';
import { ExecutionResourceProvider } from './ExecutionResourceProvider'; import { ExecutionResourceProvider } from './ExecutionResourceProvider';
import { WorkflowLink } from './WorkflowLink';
import OpenDrawer from './components/OpenDrawer';
import { workflowSchema } from './schemas/workflows'; import { workflowSchema } from './schemas/workflows';
import { ExecutionStatusSelect, ExecutionStatusColumn } from './components/ExecutionStatus'; import { WorkflowLink } from './WorkflowLink';
import WorkflowPlugin, { RadioWithTooltip } from '.';
import { onFieldChange } from '@tachybase/schema';
import { useField, useFormEffects } from '@tachybase/schema';
function SyncOptionSelect(props) { function SyncOptionSelect(props) {
const field = useField<any>(); const field = useField<any>();

View File

@ -1,7 +1,8 @@
import { QuestionCircleOutlined } from '@ant-design/icons';
import { css, useCompile } from '@tachybase/client';
import { Radio, Space, Tooltip } from 'antd';
import React from 'react'; import React from 'react';
import { css, useCompile } from '@tachybase/client';
import { QuestionCircleOutlined } from '@ant-design/icons';
import { Radio, Space, Tooltip } from 'antd';
export interface RadioWithTooltipOption { export interface RadioWithTooltipOption {
value: any; value: any;

View File

@ -1,12 +1,12 @@
import { SchemaInitializerItemType } from '@tachybase/client'; import { SchemaInitializerItemType } from '@tachybase/client';
import { Evaluator, evaluators, getOptions } from '@tachybase/evaluators/client'; import { Evaluator, evaluators, getOptions } from '@tachybase/evaluators/client';
import { RadioWithTooltip } from '../components/RadioWithTooltip';
import { ValueBlock } from '../components/ValueBlock';
import { renderEngineReference } from '../components/renderEngineReference';
import { NAMESPACE, lang } from '../locale';
import { BaseTypeSets, WorkflowVariableTextArea, defaultFieldNames } from '../variable';
import { Instruction } from '.'; import { Instruction } from '.';
import { RadioWithTooltip } from '../components/RadioWithTooltip';
import { renderEngineReference } from '../components/renderEngineReference';
import { ValueBlock } from '../components/ValueBlock';
import { lang, NAMESPACE } from '../locale';
import { BaseTypeSets, defaultFieldNames, WorkflowVariableTextArea } from '../variable';
export default class extends Instruction { export default class extends Instruction {
title = `{{t("Calculation", { ns: "${NAMESPACE}" })}}`; title = `{{t("Calculation", { ns: "${NAMESPACE}" })}}`;

View File

@ -1,10 +1,12 @@
import { CloseCircleOutlined } from '@ant-design/icons'; import React from 'react';
import { css, cx, useCompile, Variable } from '@tachybase/client'; import { css, cx, useCompile, Variable } from '@tachybase/client';
import { evaluators } from '@tachybase/evaluators/client'; import { evaluators } from '@tachybase/evaluators/client';
import { Registry } from '@tachybase/utils/client'; import { Registry } from '@tachybase/utils/client';
import { CloseCircleOutlined } from '@ant-design/icons';
import { Button, Select } from 'antd'; import { Button, Select } from 'antd';
import React from 'react';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import { Instruction, NodeDefaultView } from '.'; import { Instruction, NodeDefaultView } from '.';
import { Branch } from '../Branch'; import { Branch } from '../Branch';
import { RadioWithTooltip, RadioWithTooltipOption } from '../components/RadioWithTooltip'; import { RadioWithTooltip, RadioWithTooltipOption } from '../components/RadioWithTooltip';

View File

@ -327,6 +327,113 @@ export function NodeDefaultView(props) {
} }
}, []); }, []);
const schema = {
type: 'void',
properties: {
...(instruction.view ? { view: instruction.view } : {}),
button: {
type: 'void',
'x-content': detailText,
'x-component': Button,
'x-component-props': {
type: 'link',
className: 'workflow-node-config-button',
},
},
[data.id]: {
type: 'void',
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-use-decorator-props': 'useFormProviderProps',
'x-component': 'Action.Area',
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
: {
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,
},
};
return ( return (
<div className={cx(styles.nodeClass, `workflow-node-type-${data.type}`)}> <div className={cx(styles.nodeClass, `workflow-node-type-${data.type}`)}>
<div <div
@ -363,112 +470,7 @@ export function NodeDefaultView(props) {
useFormProviderProps, useFormProviderProps,
}} }}
components={instruction.components} components={instruction.components}
schema={{ schema={schema}
type: 'void',
properties: {
...(instruction.view ? { view: instruction.view } : {}),
button: {
type: 'void',
'x-content': detailText,
'x-component': Button,
'x-component-props': {
type: 'link',
className: 'workflow-node-config-button',
},
},
[data.id]: {
type: 'void',
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-use-decorator-props': 'useFormProviderProps',
'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
: {
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,
},
}}
/> />
</FormProvider> </FormProvider>
</ActionContextProvider> </ActionContextProvider>

View File

@ -1,16 +1,14 @@
import { useField, useForm } from '@tachybase/schema';
import React from 'react'; import React from 'react';
import { useCollectionDataSource, useCollectionManager_deprecated } from '@tachybase/client'; import { useCollectionDataSource, useCollectionManager_deprecated } from '@tachybase/client';
import { useField, useForm } from '@tachybase/schema';
import { Instruction } from '.';
import CollectionFieldset from '../components/CollectionFieldset'; import CollectionFieldset from '../components/CollectionFieldset';
import { FilterDynamicComponent } from '../components/FilterDynamicComponent'; import { FilterDynamicComponent } from '../components/FilterDynamicComponent';
import { RadioWithTooltip } from '../components/RadioWithTooltip'; import { RadioWithTooltip } from '../components/RadioWithTooltip';
import { NAMESPACE, lang } from '../locale'; import { lang, NAMESPACE } from '../locale';
import { collection, filter, values } from '../schemas/collection'; import { collection, filter, values } from '../schemas/collection';
import { isValidFilter } from '../utils'; import { isValidFilter } from '../utils';
import { Instruction } from '.';
function IndividualHooksRadioWithTooltip({ onChange, ...props }) { function IndividualHooksRadioWithTooltip({ onChange, ...props }) {
const { getCollectionFields } = useCollectionManager_deprecated(); const { getCollectionFields } = useCollectionManager_deprecated();

View File

@ -1,10 +1,10 @@
import React from 'react'; import React from 'react';
import { ISchema } from '@tachybase/schema';
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { message } from 'antd';
import { useActionContext, useRecord, useResourceActionContext, useResourceContext } from '@tachybase/client'; import { useActionContext, useRecord, useResourceActionContext, useResourceContext } from '@tachybase/client';
import { ISchema } from '@tachybase/schema';
import { message } from 'antd';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { ExecutionStatusOptions } from '../constants'; import { ExecutionStatusOptions } from '../constants';
import { NAMESPACE } from '../locale'; import { NAMESPACE } from '../locale';

View File

@ -35,6 +35,18 @@ const useStyles = createStyles(({ css, token }) => {
} }
} }
.workflow-content {
display: flex;
height: 100%;
padding-bottom: 40px;
}
.workflow-operator-area {
padding: 8px;
background-color: white;
width: 800px;
}
.workflow-canvas-wrapper { .workflow-canvas-wrapper {
flex-grow: 1; flex-grow: 1;
overflow: hidden; overflow: hidden;

View File

@ -1,29 +1,28 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Button, Input, Tag, message } from 'antd';
import { cloneDeep } from 'lodash';
import { InfoOutlined } from '@ant-design/icons';
import { createForm } from '@tachybase/schema';
import { ISchema, useForm } from '@tachybase/schema';
import { import {
ActionContextProvider, ActionContextProvider,
css,
cx,
FieldNames, FieldNames,
FormProvider, FormProvider,
SchemaComponent, SchemaComponent,
SchemaInitializerItemType, SchemaInitializerItemType,
css,
cx,
useAPIClient,
useActionContext, useActionContext,
useAPIClient,
useCompile, useCompile,
usePlugin, usePlugin,
useResourceActionContext, useResourceActionContext,
} from '@tachybase/client'; } from '@tachybase/client';
import { createForm, ISchema, useForm } from '@tachybase/schema';
import { InfoOutlined } from '@ant-design/icons';
import { Button, Input, message, Tag } from 'antd';
import { cloneDeep } from 'lodash';
import WorkflowPlugin from '..'; import WorkflowPlugin from '..';
import { useFlowContext } from '../FlowContext';
import { DrawerDescription } from '../components/DrawerDescription'; import { DrawerDescription } from '../components/DrawerDescription';
import { NAMESPACE, lang } from '../locale'; import { useFlowContext } from '../FlowContext';
import { lang, NAMESPACE } from '../locale';
import useStyles from '../style'; import useStyles from '../style';
import { UseVariableOptions, VariableOption } from '../variable'; import { UseVariableOptions, VariableOption } from '../variable';
@ -264,7 +263,7 @@ export const TriggerConfig = () => {
drawer: { drawer: {
type: 'void', type: 'void',
title: titleText, title: titleText,
'x-component': 'Action.Drawer', 'x-component': 'Action.Area',
'x-decorator': 'FormV2', 'x-decorator': 'FormV2',
'x-use-decorator-props': 'useFormProviderProps', 'x-use-decorator-props': 'useFormProviderProps',
properties: { properties: {