feat(client): plugin manager toolbar
This commit is contained in:
parent
e7918b7887
commit
d1f9d3e25d
65
packages/client/src/acl/ACLAction.tsx
Normal file
65
packages/client/src/acl/ACLAction.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import { Menu } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import { LockOutlined } from '@ant-design/icons';
|
||||
import { SchemaComponent, useActionVisible, VisibleContext } from '../schema-component';
|
||||
import { ISchema, useForm } from '@formily/react';
|
||||
|
||||
const useCloseAction = () => {
|
||||
const { setVisible } = useActionVisible();
|
||||
const form = useForm();
|
||||
return {
|
||||
async run() {
|
||||
setVisible(false);
|
||||
form.submit((values) => {
|
||||
console.log(values);
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const schema: ISchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
drawer1: {
|
||||
'x-component': 'Action.Drawer',
|
||||
type: 'void',
|
||||
title: 'Drawer Title',
|
||||
properties: {
|
||||
hello1: {
|
||||
'x-content': 'Hello',
|
||||
title: 'T1',
|
||||
},
|
||||
footer1: {
|
||||
'x-component': 'Action.Drawer.Footer',
|
||||
type: 'void',
|
||||
properties: {
|
||||
close1: {
|
||||
title: 'Close',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
useAction: '{{ useCloseAction }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const ACLAction = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
return (
|
||||
<VisibleContext.Provider value={[visible, setVisible]}>
|
||||
<Menu.Item
|
||||
eventKey={'ACLAction'}
|
||||
onClick={() => {
|
||||
setVisible(true);
|
||||
}}
|
||||
>
|
||||
<LockOutlined />
|
||||
</Menu.Item>
|
||||
<SchemaComponent scope={{ useCloseAction }} schema={schema} />
|
||||
</VisibleContext.Provider>
|
||||
);
|
||||
};
|
@ -1,2 +1,3 @@
|
||||
export * from './ACLProvider';
|
||||
export * from './RolePermissionManager';
|
||||
export * from './ACLAction';
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
AntdConfigProvider,
|
||||
SchemaComponentProvider,
|
||||
Menu,
|
||||
Action,
|
||||
} from '@nocobase/client';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { Spin } from 'antd';
|
||||
@ -23,7 +24,7 @@ const providers = [
|
||||
[APIClientProvider, { apiClient }],
|
||||
[I18nextProvider, { i18n }],
|
||||
[AntdConfigProvider, { remoteLocale: true }],
|
||||
[SchemaComponentProvider, { components: { Menu } }],
|
||||
[SchemaComponentProvider, { components: { Menu, Action } }],
|
||||
[RouteSwitchProvider, { components: { AuthLayout, AdminLayout } }],
|
||||
];
|
||||
|
||||
|
@ -0,0 +1,65 @@
|
||||
import { Menu } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import { DatabaseOutlined } from '@ant-design/icons';
|
||||
import { SchemaComponent, useActionVisible, VisibleContext } from '../schema-component';
|
||||
import { ISchema, useForm } from '@formily/react';
|
||||
|
||||
const useCloseAction = () => {
|
||||
const { setVisible } = useActionVisible();
|
||||
const form = useForm();
|
||||
return {
|
||||
async run() {
|
||||
setVisible(false);
|
||||
form.submit((values) => {
|
||||
console.log(values);
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const schema: ISchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
drawer1: {
|
||||
'x-component': 'Action.Drawer',
|
||||
type: 'void',
|
||||
title: 'Drawer Title',
|
||||
properties: {
|
||||
hello1: {
|
||||
'x-content': 'Hello',
|
||||
title: 'T1',
|
||||
},
|
||||
footer1: {
|
||||
'x-component': 'Action.Drawer.Footer',
|
||||
type: 'void',
|
||||
properties: {
|
||||
close1: {
|
||||
title: 'Close',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
useAction: '{{ useCloseAction }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const CollectionManagerAction = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
return (
|
||||
<VisibleContext.Provider value={[visible, setVisible]}>
|
||||
<Menu.Item
|
||||
eventKey={'CollectionManagerAction'}
|
||||
onClick={() => {
|
||||
setVisible(true);
|
||||
}}
|
||||
>
|
||||
<DatabaseOutlined />
|
||||
</Menu.Item>
|
||||
<SchemaComponent scope={{ useCloseAction }} schema={schema} />
|
||||
</VisibleContext.Provider>
|
||||
);
|
||||
};
|
@ -5,3 +5,4 @@ export * from './context';
|
||||
export * from './hooks';
|
||||
export * from './types';
|
||||
export * from './CollectionField';
|
||||
export * from './CollectionManagerAction';
|
||||
|
65
packages/client/src/current-user/ProfileAction.tsx
Normal file
65
packages/client/src/current-user/ProfileAction.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import { Menu } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import { DatabaseOutlined } from '@ant-design/icons';
|
||||
import { SchemaComponent, useActionVisible, VisibleContext } from '../schema-component';
|
||||
import { ISchema, useForm } from '@formily/react';
|
||||
|
||||
const useCloseAction = () => {
|
||||
const { setVisible } = useActionVisible();
|
||||
const form = useForm();
|
||||
return {
|
||||
async run() {
|
||||
setVisible(false);
|
||||
form.submit((values) => {
|
||||
console.log(values);
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const schema: ISchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
drawer1: {
|
||||
'x-component': 'Action.Drawer',
|
||||
type: 'void',
|
||||
title: 'Drawer Title',
|
||||
properties: {
|
||||
hello1: {
|
||||
'x-content': 'Hello',
|
||||
title: 'T1',
|
||||
},
|
||||
footer1: {
|
||||
'x-component': 'Action.Drawer.Footer',
|
||||
type: 'void',
|
||||
properties: {
|
||||
close1: {
|
||||
title: 'Close',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
useAction: '{{ useCloseAction }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const ProfileAction = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
return (
|
||||
<VisibleContext.Provider value={[visible, setVisible]}>
|
||||
<Menu.Item
|
||||
eventKey={'ProfileAction'}
|
||||
onClick={() => {
|
||||
setVisible(true);
|
||||
}}
|
||||
>
|
||||
个人资料
|
||||
</Menu.Item>
|
||||
<SchemaComponent scope={{ useCloseAction }} schema={schema} />
|
||||
</VisibleContext.Provider>
|
||||
);
|
||||
};
|
@ -1,3 +1,19 @@
|
||||
export function CurrentUser() {
|
||||
import { Menu } from 'antd';
|
||||
import React from 'react';
|
||||
import { MoreOutlined, DesktopOutlined } from '@ant-design/icons';
|
||||
import { ProfileAction } from './ProfileAction';
|
||||
|
||||
}
|
||||
export const CurrentUser = () => null;
|
||||
|
||||
CurrentUser.Dropdown = () => {
|
||||
return (
|
||||
// @ts-ignore
|
||||
<Menu.SubMenu eventKey={'CurrentUser.Dropdown'} title={'超级管理员'}>
|
||||
<ProfileAction />
|
||||
<Menu.Item>切换角色</Menu.Item>
|
||||
<Menu.Item>语言设置</Menu.Item>
|
||||
<Menu.Divider />
|
||||
<Menu.Item>注销</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
);
|
||||
};
|
||||
|
@ -14,3 +14,4 @@ export * from './system-settings';
|
||||
export * from './application';
|
||||
export * from './record-provider';
|
||||
export * from './async-data-provider';
|
||||
export * from './plugin-manager';
|
||||
|
53
packages/client/src/plugin-manager/PluginManager.tsx
Normal file
53
packages/client/src/plugin-manager/PluginManager.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import React, { createContext } from 'react';
|
||||
import { Menu, Space } from 'antd';
|
||||
import { SettingOutlined, MoreOutlined, DesktopOutlined } from '@ant-design/icons';
|
||||
import { CurrentUser, DesignableSwitch, CollectionManagerAction, ACLAction, SystemSettings } from '../';
|
||||
import { get } from 'lodash';
|
||||
import { PluginManagerProvider } from '.';
|
||||
|
||||
export const PluginManager = () => null;
|
||||
|
||||
PluginManager.Provider = PluginManagerProvider;
|
||||
|
||||
PluginManager.Toolbar = (props: any) => {
|
||||
const components = { CurrentUser, DesignableSwitch, CollectionManagerAction, ACLAction, SystemSettings };
|
||||
const items = [
|
||||
{
|
||||
action: 'DesignableSwitch',
|
||||
pin: true,
|
||||
},
|
||||
{
|
||||
action: 'CollectionManagerAction',
|
||||
pin: true,
|
||||
},
|
||||
{
|
||||
action: 'ACLAction',
|
||||
pin: true,
|
||||
},
|
||||
{
|
||||
action: 'SystemSettings.Action',
|
||||
},
|
||||
];
|
||||
const CurrentUserDropdown = get(components, 'CurrentUser.Dropdown');
|
||||
return (
|
||||
<Menu selectable={false} mode={'horizontal'} theme={'dark'}>
|
||||
{items
|
||||
.filter((item) => item.pin)
|
||||
.map((item) => {
|
||||
const Action = get(components, item.action);
|
||||
return Action && <Action />;
|
||||
})}
|
||||
<Menu.SubMenu key={'more'} title={<MoreOutlined />}>
|
||||
{items
|
||||
.filter((item) => !item.pin)
|
||||
.map((item) => {
|
||||
const Action = get(components, item.action);
|
||||
return Action && <Action />;
|
||||
})}
|
||||
<Menu.Divider></Menu.Divider>
|
||||
<Menu.Item icon={<SettingOutlined />}>管理插件</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
<CurrentUserDropdown />
|
||||
</Menu>
|
||||
);
|
||||
};
|
@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
import { PluginManagerContext } from './context';
|
||||
|
||||
export const PluginManagerProvider: React.FC<any> = (props) => {
|
||||
const { components, children } = props;
|
||||
return <PluginManagerContext.Provider value={{ components }}>{children}</PluginManagerContext.Provider>;
|
||||
};
|
3
packages/client/src/plugin-manager/context.ts
Normal file
3
packages/client/src/plugin-manager/context.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
export const PluginManagerContext = createContext<any>({});
|
55
packages/client/src/plugin-manager/index.md
Normal file
55
packages/client/src/plugin-manager/index.md
Normal file
@ -0,0 +1,55 @@
|
||||
---
|
||||
nav:
|
||||
path: /client
|
||||
group:
|
||||
path: /client
|
||||
---
|
||||
|
||||
# PluginManager
|
||||
|
||||
```tsx | pure
|
||||
<PluginManager.Provider components={{
|
||||
CurrentUser,
|
||||
DesignableSwitch,
|
||||
CollectionManagerAction,
|
||||
ACLAction,
|
||||
SystemSettings,
|
||||
}}>
|
||||
<PluginManager.Toolbar items={[
|
||||
{
|
||||
action: 'DesignableSwitch',
|
||||
pin: true,
|
||||
},
|
||||
{
|
||||
action: 'CollectionManagerAction',
|
||||
pin: true,
|
||||
},
|
||||
{
|
||||
action: 'ACLAction',
|
||||
pin: true,
|
||||
},
|
||||
{
|
||||
action: 'SystemSettings.Action',
|
||||
},
|
||||
]}/>
|
||||
</PluginManager.Provider>
|
||||
```
|
||||
|
||||
## 扩展
|
||||
|
||||
如何扩展插件的 `PluginManager.Toolbar.Item`
|
||||
|
||||
```tsx | pure
|
||||
import React from 'react';
|
||||
import { Button, Menu } from 'antd';
|
||||
import { HighlightOutlined } from '@ant-design/icons';
|
||||
|
||||
export const DesignableSwitch = () => {
|
||||
const { designable, setDesignable } = useDesignable();
|
||||
return (
|
||||
<Menu.Item key={'DesignableSwitch'} eventKey={'DesignableSwitch'}>
|
||||
<HighlightOutlined />
|
||||
</Menu.Item>
|
||||
);
|
||||
};
|
||||
```
|
3
packages/client/src/plugin-manager/index.ts
Normal file
3
packages/client/src/plugin-manager/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './context';
|
||||
export * from './PluginManager';
|
||||
export * from './PluginManagerProvider';
|
@ -14,12 +14,12 @@ group:
|
||||
- Layout.Header
|
||||
- SiteTitle
|
||||
- Menu(SchemaComponent)
|
||||
- PluginActionBar
|
||||
- Designable.Action(引用)
|
||||
- CollectionManager.Action(引用)
|
||||
- ACL.Action(引用)
|
||||
- SystemSettings.Action(引用)
|
||||
- CurrentUser.Dropdown(引用)
|
||||
- PluginManager.Toolbar
|
||||
- DesignableAction
|
||||
- CollectionManager.Action
|
||||
- ACL.Action
|
||||
- SystemSettings.Action
|
||||
- CurrentUser.Dropdown
|
||||
- Layout.Sider
|
||||
- SideMenu(随 Menu 联动的)
|
||||
- Layout.Content
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { Layout, Spin } from 'antd';
|
||||
import { useRoute } from '../..';
|
||||
import { findMenuItem, RemoteSchemaComponent } from '../../../schema-component';
|
||||
import { Redirect, useHistory, useRouteMatch } from 'react-router-dom';
|
||||
import { Layout } from 'antd';
|
||||
import { useRoute } from '../../hooks';
|
||||
import { useHistory, useRouteMatch } from 'react-router-dom';
|
||||
import { findMenuItem, RemoteSchemaComponent, PluginManager } from '../../../';
|
||||
|
||||
export function AdminLayout(props: any) {
|
||||
const route = useRoute();
|
||||
@ -19,28 +19,33 @@ export function AdminLayout(props: any) {
|
||||
const [hidden, setHidden] = useState(false);
|
||||
return (
|
||||
<Layout>
|
||||
<Layout.Header>
|
||||
<RemoteSchemaComponent
|
||||
hidden={hidden}
|
||||
uid={route.uiSchemaUid}
|
||||
scope={{ onSelect, sideMenuRef, defaultSelectedUid }}
|
||||
schemaTransform={(data) => {
|
||||
data['x-component-props']['defaultSelectedUid'] = defaultSelectedUid;
|
||||
return data;
|
||||
}}
|
||||
onSuccess={(data) => {
|
||||
if (defaultSelectedUid) {
|
||||
return;
|
||||
}
|
||||
setHidden(true);
|
||||
setTimeout(() => setHidden(false), 11);
|
||||
const s = findMenuItem(data?.data);
|
||||
if (s) {
|
||||
setSchema(s);
|
||||
history.push(`/admin/${s['x-uid']}`);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Layout.Header style={{ position: 'relative' }}>
|
||||
<div>
|
||||
<RemoteSchemaComponent
|
||||
hidden={hidden}
|
||||
uid={route.uiSchemaUid}
|
||||
scope={{ onSelect, sideMenuRef, defaultSelectedUid }}
|
||||
schemaTransform={(data) => {
|
||||
data['x-component-props']['defaultSelectedUid'] = defaultSelectedUid;
|
||||
return data;
|
||||
}}
|
||||
onSuccess={(data) => {
|
||||
if (defaultSelectedUid) {
|
||||
return;
|
||||
}
|
||||
setHidden(true);
|
||||
setTimeout(() => setHidden(false), 11);
|
||||
const s = findMenuItem(data?.data);
|
||||
if (s) {
|
||||
setSchema(s);
|
||||
history.push(`/admin/${s['x-uid']}`);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ position: 'absolute', top: 0, right: 0 }}>
|
||||
<PluginManager.Toolbar />
|
||||
</div>
|
||||
</Layout.Header>
|
||||
<Layout>
|
||||
<Layout.Sider style={{ display: 'none' }} theme={'light'} ref={sideMenuRef}></Layout.Sider>
|
||||
|
@ -0,0 +1,67 @@
|
||||
import React, { useState } from 'react';
|
||||
import { observer, ISchema, useForm } from '@formily/react';
|
||||
import {
|
||||
SchemaComponent,
|
||||
SchemaComponentProvider,
|
||||
Form,
|
||||
Action,
|
||||
useActionVisible,
|
||||
VisibleContext,
|
||||
} from '@nocobase/client';
|
||||
import { FormItem, Input } from '@formily/antd';
|
||||
import { Button } from 'antd';
|
||||
|
||||
const useCloseAction = () => {
|
||||
const { setVisible } = useActionVisible();
|
||||
const form = useForm();
|
||||
return {
|
||||
async run() {
|
||||
setVisible(false);
|
||||
form.submit((values) => {
|
||||
console.log(values);
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const schema: ISchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
drawer1: {
|
||||
'x-component': 'Action.Drawer',
|
||||
type: 'void',
|
||||
title: 'Drawer Title',
|
||||
properties: {
|
||||
hello1: {
|
||||
'x-content': 'Hello',
|
||||
title: 'T1',
|
||||
},
|
||||
footer1: {
|
||||
'x-component': 'Action.Drawer.Footer',
|
||||
type: 'void',
|
||||
properties: {
|
||||
close1: {
|
||||
title: 'Close',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
useAction: '{{ useCloseAction }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default observer(() => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
return (
|
||||
<SchemaComponentProvider components={{ Form, Action, Input, FormItem }}>
|
||||
<VisibleContext.Provider value={[visible, setVisible]}>
|
||||
<a onClick={() => setVisible(true)}>Open</a>
|
||||
<SchemaComponent scope={{ useCloseAction }} schema={schema} />
|
||||
</VisibleContext.Provider>
|
||||
</SchemaComponentProvider>
|
||||
);
|
||||
});
|
@ -30,4 +30,12 @@ group:
|
||||
|
||||
## Examples
|
||||
|
||||
### Action + Action.Drawer
|
||||
|
||||
<code src="./demos/demo1.tsx"/>
|
||||
|
||||
### VisibleContext + Action.Drawer
|
||||
|
||||
只配置 Action.Drawer,而不需要 Action,通过 VisibleContext 自定义按钮。
|
||||
|
||||
<code src="./demos/demo2.tsx"/>
|
||||
|
@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import { Button, Menu } from 'antd';
|
||||
import { HighlightOutlined } from '@ant-design/icons';
|
||||
import cls from 'classnames';
|
||||
import { useDesignable } from '..';
|
||||
|
||||
export const DesignableSwitch = () => {
|
||||
const { designable, setDesignable } = useDesignable();
|
||||
return (
|
||||
<Menu.Item key={'DesignableSwitch'} eventKey={'DesignableSwitch'}>
|
||||
<HighlightOutlined />
|
||||
</Menu.Item>
|
||||
);
|
||||
};
|
@ -3,3 +3,4 @@ export * from './SchemaComponent';
|
||||
export * from './SchemaComponentProvider';
|
||||
export * from './SchemaOptionsExpressionScopeProvider';
|
||||
export * from './RemoteSchemaComponent';
|
||||
export * from './DesignableSwitch';
|
||||
|
@ -216,7 +216,7 @@ export class Designable {
|
||||
|
||||
// TODO
|
||||
export function useDesignable() {
|
||||
const { designable, refresh, reset } = useContext(SchemaComponentContext);
|
||||
const { designable, setDesignable, refresh, reset } = useContext(SchemaComponentContext);
|
||||
const DesignableBar = () => {
|
||||
return <></>;
|
||||
};
|
||||
@ -229,6 +229,7 @@ export function useDesignable() {
|
||||
designable,
|
||||
reset,
|
||||
refresh,
|
||||
setDesignable,
|
||||
DesignableBar,
|
||||
on: dn.on.bind(dn),
|
||||
// TODO
|
||||
|
65
packages/client/src/system-settings/SystemSettingsAction.tsx
Normal file
65
packages/client/src/system-settings/SystemSettingsAction.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import { Menu } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import { LockOutlined } from '@ant-design/icons';
|
||||
import { SchemaComponent, useActionVisible, VisibleContext } from '../schema-component';
|
||||
import { ISchema, useForm } from '@formily/react';
|
||||
|
||||
const useCloseAction = () => {
|
||||
const { setVisible } = useActionVisible();
|
||||
const form = useForm();
|
||||
return {
|
||||
async run() {
|
||||
setVisible(false);
|
||||
form.submit((values) => {
|
||||
console.log(values);
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const schema: ISchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
drawer1: {
|
||||
'x-component': 'Action.Drawer',
|
||||
type: 'void',
|
||||
title: 'Drawer Title',
|
||||
properties: {
|
||||
hello1: {
|
||||
'x-content': 'Hello',
|
||||
title: 'T1',
|
||||
},
|
||||
footer1: {
|
||||
'x-component': 'Action.Drawer.Footer',
|
||||
type: 'void',
|
||||
properties: {
|
||||
close1: {
|
||||
title: 'Close',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
useAction: '{{ useCloseAction }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const SystemSettingsAction = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
return (
|
||||
<VisibleContext.Provider value={[visible, setVisible]}>
|
||||
<Menu.Item
|
||||
eventKey={'ACLAction'}
|
||||
onClick={() => {
|
||||
setVisible(true);
|
||||
}}
|
||||
>
|
||||
<LockOutlined /> 系统设置
|
||||
</Menu.Item>
|
||||
<SchemaComponent scope={{ useCloseAction }} schema={schema} />
|
||||
</VisibleContext.Provider>
|
||||
);
|
||||
};
|
@ -1,3 +1,5 @@
|
||||
export function SystemSettings() {
|
||||
import { SystemSettingsAction } from './SystemSettingsAction';
|
||||
|
||||
}
|
||||
export const SystemSettings = () => null;
|
||||
|
||||
SystemSettings.Action = SystemSettingsAction;
|
||||
|
Loading…
Reference in New Issue
Block a user