mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 13:39:24 +08:00
fix: formDrawer theme context issue (#6403)
This commit is contained in:
parent
e5507d0758
commit
8741c26a86
@ -0,0 +1,214 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of the NocoBase (R) project.
|
||||||
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||||
|
* Authors: NocoBase Team.
|
||||||
|
*
|
||||||
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||||
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { Fragment, useLayoutEffect, useRef, useState } from 'react';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
import { createForm, IFormProps, Form, onFormSubmitSuccess } from '@formily/core';
|
||||||
|
import { toJS } from '@formily/reactive';
|
||||||
|
import { FormProvider, observer, ReactFC } from '@formily/react';
|
||||||
|
import { isNum, isStr, isBool, isFn, applyMiddleware, IMiddleware } from '@formily/shared';
|
||||||
|
import { Drawer, DrawerProps, ThemeConfig } from 'antd';
|
||||||
|
import { usePrefixCls, createPortalProvider, createPortalRoot, loading } from '../__builtins__';
|
||||||
|
import { GlobalThemeProvider } from '../../../global-theme';
|
||||||
|
|
||||||
|
type FormDrawerRenderer = React.ReactElement | ((form: Form) => React.ReactElement);
|
||||||
|
|
||||||
|
type DrawerTitle = string | number | React.ReactElement;
|
||||||
|
|
||||||
|
type EventType = React.KeyboardEvent<HTMLDivElement> | React.MouseEvent<HTMLDivElement | HTMLButtonElement>;
|
||||||
|
|
||||||
|
const isDrawerTitle = (props: any): props is DrawerTitle => {
|
||||||
|
return isNum(props) || isStr(props) || isBool(props) || React.isValidElement(props);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDrawerProps = (props: any): IDrawerProps => {
|
||||||
|
if (isDrawerTitle(props)) {
|
||||||
|
return {
|
||||||
|
title: props,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface IFormDrawer {
|
||||||
|
forOpen(middleware: IMiddleware<IFormProps>): IFormDrawer;
|
||||||
|
open(props?: IFormProps): Promise<any>;
|
||||||
|
close(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDrawerProps extends DrawerProps {
|
||||||
|
onClose?: (e: EventType) => void | boolean;
|
||||||
|
loadingText?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FormDrawer(title: IDrawerProps, id: string, renderer: FormDrawerRenderer): IFormDrawer;
|
||||||
|
export function FormDrawer(title: IDrawerProps, id: FormDrawerRenderer): IFormDrawer;
|
||||||
|
export function FormDrawer(title: DrawerTitle, id: string, renderer: FormDrawerRenderer): IFormDrawer;
|
||||||
|
export function FormDrawer(
|
||||||
|
title: IDrawerProps,
|
||||||
|
id: string,
|
||||||
|
renderer: FormDrawerRenderer,
|
||||||
|
theme: ThemeConfig,
|
||||||
|
): IFormDrawer;
|
||||||
|
|
||||||
|
export function FormDrawer(title: DrawerTitle, id: FormDrawerRenderer): IFormDrawer;
|
||||||
|
export function FormDrawer(title: any, id: any, renderer?: any, theme?: any): IFormDrawer {
|
||||||
|
if (isFn(id) || React.isValidElement(id)) {
|
||||||
|
theme = renderer;
|
||||||
|
renderer = id;
|
||||||
|
id = 'form-drawer';
|
||||||
|
}
|
||||||
|
const env = {
|
||||||
|
host: document.createElement('div'),
|
||||||
|
openMiddlewares: [],
|
||||||
|
form: null,
|
||||||
|
promise: null,
|
||||||
|
};
|
||||||
|
const root = createPortalRoot(env.host, id);
|
||||||
|
const props = getDrawerProps(title);
|
||||||
|
const drawer = {
|
||||||
|
width: '40%',
|
||||||
|
...props,
|
||||||
|
onClose: (e: any) => {
|
||||||
|
if (props?.onClose?.(e) !== false) {
|
||||||
|
formDrawer.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
afterVisibleChange: (visible: boolean) => {
|
||||||
|
props?.afterVisibleChange?.(visible);
|
||||||
|
if (visible) return;
|
||||||
|
root.unmount();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const DrawerContent = observer(() => {
|
||||||
|
return <Fragment>{isFn(renderer) ? renderer(env.form) : renderer}</Fragment>;
|
||||||
|
});
|
||||||
|
const renderDrawer = (visible = true) => {
|
||||||
|
return (
|
||||||
|
//@ts-ignore
|
||||||
|
<GlobalThemeProvider theme={theme}>
|
||||||
|
<Drawer {...drawer} visible={visible}>
|
||||||
|
<FormProvider form={env.form}>
|
||||||
|
<DrawerContent />
|
||||||
|
</FormProvider>
|
||||||
|
</Drawer>
|
||||||
|
</GlobalThemeProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.body.appendChild(env.host);
|
||||||
|
const formDrawer = {
|
||||||
|
forOpen: (middleware: IMiddleware<IFormProps>) => {
|
||||||
|
if (isFn(middleware)) {
|
||||||
|
env.openMiddlewares.push(middleware);
|
||||||
|
}
|
||||||
|
return formDrawer;
|
||||||
|
},
|
||||||
|
open: (props: IFormProps) => {
|
||||||
|
if (env.promise) return env.promise;
|
||||||
|
// eslint-disable-next-line no-async-promise-executor
|
||||||
|
env.promise = new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
props = await loading(drawer.loadingText, () => applyMiddleware(props, env.openMiddlewares));
|
||||||
|
env.form =
|
||||||
|
env.form ||
|
||||||
|
createForm({
|
||||||
|
...props,
|
||||||
|
effects(form) {
|
||||||
|
onFormSubmitSuccess(() => {
|
||||||
|
resolve(toJS(form.values));
|
||||||
|
formDrawer.close();
|
||||||
|
});
|
||||||
|
props?.effects?.(form);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
root.render(() => renderDrawer(false));
|
||||||
|
setTimeout(() => {
|
||||||
|
root.render(() => renderDrawer(true));
|
||||||
|
}, 16);
|
||||||
|
});
|
||||||
|
return env.promise;
|
||||||
|
},
|
||||||
|
close: () => {
|
||||||
|
if (!env.host) return;
|
||||||
|
root.render(() => renderDrawer(false));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return formDrawer;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DrawerExtra: ReactFC = (props) => {
|
||||||
|
const ref = useRef<HTMLDivElement>();
|
||||||
|
const [extra, setExtra] = useState<HTMLDivElement>();
|
||||||
|
const extraRef = useRef<HTMLDivElement>();
|
||||||
|
const prefixCls = usePrefixCls('drawer');
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const content = ref.current?.closest(`.${prefixCls}-wrapper-body`)?.querySelector(`.${prefixCls}-header`);
|
||||||
|
if (content) {
|
||||||
|
if (!extraRef.current) {
|
||||||
|
extraRef.current = content.querySelector(`.${prefixCls}-extra`);
|
||||||
|
if (!extraRef.current) {
|
||||||
|
extraRef.current = document.createElement('div');
|
||||||
|
extraRef.current.classList.add(`${prefixCls}-extra`);
|
||||||
|
content.appendChild(extraRef.current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setExtra(extraRef.current);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
extraRef.current = extra;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref} style={{ display: 'none' }}>
|
||||||
|
{extra && createPortal(props.children, extra)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DrawerFooter: ReactFC = (props) => {
|
||||||
|
const ref = useRef<HTMLDivElement>();
|
||||||
|
const [footer, setFooter] = useState<HTMLDivElement>();
|
||||||
|
const footerRef = useRef<HTMLDivElement>();
|
||||||
|
const prefixCls = usePrefixCls('drawer');
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const content = ref.current?.closest(`.${prefixCls}-wrapper-body`);
|
||||||
|
if (content) {
|
||||||
|
if (!footerRef.current) {
|
||||||
|
footerRef.current = content.querySelector(`.${prefixCls}-footer`);
|
||||||
|
if (!footerRef.current) {
|
||||||
|
footerRef.current = document.createElement('div');
|
||||||
|
footerRef.current.classList.add(`${prefixCls}-footer`);
|
||||||
|
content.appendChild(footerRef.current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setFooter(footerRef.current);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
footerRef.current = footer;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref} style={{ display: 'none' }}>
|
||||||
|
{footer && createPortal(props.children, footer)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
FormDrawer.Extra = DrawerExtra;
|
||||||
|
|
||||||
|
FormDrawer.Footer = DrawerFooter;
|
||||||
|
|
||||||
|
FormDrawer.Portal = createPortalProvider('form-drawer');
|
||||||
|
|
||||||
|
export default FormDrawer;
|
@ -65,5 +65,5 @@ export * from './tree-select';
|
|||||||
export * from './unix-timestamp';
|
export * from './unix-timestamp';
|
||||||
export * from './upload';
|
export * from './upload';
|
||||||
export * from './variable';
|
export * from './variable';
|
||||||
|
export * from './form-drawer';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
|
@ -7,20 +7,10 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
import { DeleteOutlined, DownOutlined, PlusOutlined, ReloadOutlined } from '@ant-design/icons';
|
import { DeleteOutlined, DownOutlined, PlusOutlined, ReloadOutlined } from '@ant-design/icons';
|
||||||
import {
|
import { Checkbox, FormButtonGroup, FormItem, FormLayout, Input, Radio, Reset, Submit } from '@formily/antd-v5';
|
||||||
Checkbox,
|
|
||||||
FormButtonGroup,
|
|
||||||
FormDrawer,
|
|
||||||
FormItem,
|
|
||||||
FormLayout,
|
|
||||||
Input,
|
|
||||||
Radio,
|
|
||||||
Reset,
|
|
||||||
Submit,
|
|
||||||
} from '@formily/antd-v5';
|
|
||||||
import { registerValidateRules } from '@formily/core';
|
import { registerValidateRules } from '@formily/core';
|
||||||
import { createSchemaField, useField } from '@formily/react';
|
import { createSchemaField, useField } from '@formily/react';
|
||||||
import { SchemaComponent, SchemaComponentOptions, useAPIClient } from '@nocobase/client';
|
import { SchemaComponent, SchemaComponentOptions, useAPIClient, FormDrawer, useGlobalTheme } from '@nocobase/client';
|
||||||
import { Alert, App, Button, Card, Dropdown, Flex, Space, Table, Tag } from 'antd';
|
import { Alert, App, Button, Card, Dropdown, Flex, Space, Table, Tag } from 'antd';
|
||||||
import React, { useContext, useState } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import { VAR_NAME_RE } from '../../re';
|
import { VAR_NAME_RE } from '../../re';
|
||||||
@ -122,6 +112,7 @@ export function EnvironmentVariables({ request, setSelectRowKeys }) {
|
|||||||
const t = useT();
|
const t = useT();
|
||||||
const api = useAPIClient();
|
const api = useAPIClient();
|
||||||
const { data, loading, refresh } = request || {};
|
const { data, loading, refresh } = request || {};
|
||||||
|
const { theme } = useGlobalTheme();
|
||||||
|
|
||||||
const typEnum = {
|
const typEnum = {
|
||||||
default: {
|
default: {
|
||||||
@ -150,40 +141,45 @@ export function EnvironmentVariables({ request, setSelectRowKeys }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleEdit = async (initialValues) => {
|
const handleEdit = async (initialValues) => {
|
||||||
const drawer = FormDrawer({ title: t('Edit') }, () => {
|
const drawer = FormDrawer(
|
||||||
return (
|
{ title: t('Edit') },
|
||||||
<FormLayout layout={'vertical'}>
|
'edit',
|
||||||
<SchemaComponentOptions scope={{ createOnly: false, t }}>
|
() => {
|
||||||
<SchemaField schema={schema} />
|
return (
|
||||||
</SchemaComponentOptions>
|
<FormLayout layout={'vertical'}>
|
||||||
<FormDrawer.Footer>
|
<SchemaComponentOptions scope={{ createOnly: false, t }}>
|
||||||
<FormButtonGroup align="right">
|
<SchemaField schema={schema} />
|
||||||
<Reset
|
</SchemaComponentOptions>
|
||||||
onClick={() => {
|
<FormDrawer.Footer>
|
||||||
drawer.close();
|
<FormButtonGroup align="right">
|
||||||
}}
|
<Reset
|
||||||
>
|
onClick={() => {
|
||||||
{t('Cancel')}
|
drawer.close();
|
||||||
</Reset>
|
}}
|
||||||
<Submit
|
>
|
||||||
onSubmit={async (data) => {
|
{t('Cancel')}
|
||||||
await api.request({
|
</Reset>
|
||||||
url: `environmentVariables:update?filterByTk=${initialValues.name}`,
|
<Submit
|
||||||
method: 'post',
|
onSubmit={async (data) => {
|
||||||
data: {
|
await api.request({
|
||||||
...data,
|
url: `environmentVariables:update?filterByTk=${initialValues.name}`,
|
||||||
},
|
method: 'post',
|
||||||
});
|
data: {
|
||||||
request.refresh();
|
...data,
|
||||||
}}
|
},
|
||||||
>
|
});
|
||||||
{t('Submit')}
|
request.refresh();
|
||||||
</Submit>
|
}}
|
||||||
</FormButtonGroup>
|
>
|
||||||
</FormDrawer.Footer>
|
{t('Submit')}
|
||||||
</FormLayout>
|
</Submit>
|
||||||
);
|
</FormButtonGroup>
|
||||||
});
|
</FormDrawer.Footer>
|
||||||
|
</FormLayout>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
theme,
|
||||||
|
);
|
||||||
drawer.open({
|
drawer.open({
|
||||||
initialValues: { ...initialValues },
|
initialValues: { ...initialValues },
|
||||||
});
|
});
|
||||||
@ -261,7 +257,7 @@ export function EnvironmentTabs() {
|
|||||||
const { variablesRequest } = useContext(EnvAndSecretsContext);
|
const { variablesRequest } = useContext(EnvAndSecretsContext);
|
||||||
const [selectRowKeys, setSelectRowKeys] = useState([]);
|
const [selectRowKeys, setSelectRowKeys] = useState([]);
|
||||||
const resource = api.resource('environmentVariables');
|
const resource = api.resource('environmentVariables');
|
||||||
|
const { theme } = useGlobalTheme();
|
||||||
const handleBulkImport = async (importData) => {
|
const handleBulkImport = async (importData) => {
|
||||||
const arr = Object.entries(importData).map(([type, dataString]) => {
|
const arr = Object.entries(importData).map(([type, dataString]) => {
|
||||||
return parseKeyValuePairs(dataString, type).filter(Boolean);
|
return parseKeyValuePairs(dataString, type).filter(Boolean);
|
||||||
@ -434,7 +430,8 @@ export function EnvironmentTabs() {
|
|||||||
{
|
{
|
||||||
variable: t('Add variable'),
|
variable: t('Add variable'),
|
||||||
bulk: t('Bulk import'),
|
bulk: t('Bulk import'),
|
||||||
}[info.key],
|
}[info.key] as any,
|
||||||
|
'add-new',
|
||||||
() => {
|
() => {
|
||||||
return (
|
return (
|
||||||
<FormLayout layout={'vertical'}>
|
<FormLayout layout={'vertical'}>
|
||||||
@ -468,6 +465,7 @@ export function EnvironmentTabs() {
|
|||||||
</FormLayout>
|
</FormLayout>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
theme,
|
||||||
)
|
)
|
||||||
.open({
|
.open({
|
||||||
initialValues: {},
|
initialValues: {},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user