feat: improve schema Initializer

This commit is contained in:
chenos 2022-02-17 23:56:39 +08:00
parent 78e89b76ef
commit b656f69565
49 changed files with 1756 additions and 346 deletions

View File

@ -5,17 +5,18 @@ import {
AntdSchemaComponentProvider,
APIClientProvider,
AuthLayout,
CollectionManagerProvider,
CollectionManagerShortcut,
compose,
DesignableSwitch,
DocumentTitleProvider,
i18n,
PluginManagerProvider,
RemoteCollectionManagerProvider,
RouteSchemaComponent,
RouteSwitch,
RouteSwitchProvider,
SchemaComponentProvider,
SchemaInitializerProvider,
SigninPage,
SignupPage,
SystemSettingsProvider,
@ -38,13 +39,32 @@ const providers = [
SystemSettingsProvider,
[
PluginManagerProvider,
{ components: { ACLShortcut, DesignableSwitch, CollectionManagerShortcut, SystemSettingsShortcut } },
{
components: {
ACLShortcut,
DesignableSwitch,
CollectionManagerShortcut,
SystemSettingsShortcut,
},
},
],
[SchemaComponentProvider, { components: { Link, NavLink } }],
CollectionManagerProvider,
RemoteCollectionManagerProvider,
SchemaInitializerProvider,
AntdSchemaComponentProvider,
[DocumentTitleProvider, { addonAfter: 'NocoBase' }],
[RouteSwitchProvider, { components: { AuthLayout, AdminLayout, RouteSchemaComponent, SigninPage, SignupPage } }],
[
RouteSwitchProvider,
{
components: {
AuthLayout,
AdminLayout,
RouteSchemaComponent,
SigninPage,
SignupPage,
},
},
],
];
const App = compose(...providers)(() => {

View File

@ -2,6 +2,7 @@ import { ISchema } from '@formily/react';
const collection = {
name: 'collections',
targetKey: 'name',
filterTargetKey: 'name',
fields: [
{
@ -42,6 +43,7 @@ export const roleCollectionsSchema: ISchema = {
type: 'void',
'x-decorator': 'ResourceActionProvider',
'x-decorator-props': {
collection,
association: {
sourceKey: 'name',
targetKey: 'name',
@ -58,10 +60,6 @@ export const roleCollectionsSchema: ISchema = {
},
},
},
'x-component': 'CollectionProvider',
'x-component-props': {
collection,
},
properties: {
table1: {
type: 'void',

View File

@ -38,9 +38,7 @@ export const roleSchema: ISchema = {
type: 'void',
'x-decorator': 'ResourceActionProvider',
'x-decorator-props': {
collection: {
targetKey: 'name',
},
collection,
resourceName: 'roles',
request: {
resource: 'roles',

View File

@ -16,6 +16,7 @@ import {
RouteSwitch,
RouteSwitchProvider,
SchemaComponentProvider,
SchemaInitializerProvider,
SigninPage,
SignupPage,
SystemSettingsProvider,
@ -42,6 +43,7 @@ const providers = [
[SchemaComponentProvider, { components: { Link, NavLink } }],
CollectionManagerProvider,
AntdSchemaComponentProvider,
SchemaInitializerProvider,
[DocumentTitleProvider, { addonAfter: 'NocoBase' }],
[RouteSwitchProvider, { components: { AuthLayout, AdminLayout, RouteSchemaComponent, SigninPage, SignupPage } }],
];

View File

@ -1,15 +1,54 @@
import { Spin } from 'antd';
import React from 'react';
import { useAPIClient, useRequest } from '../api-client';
import { CollectionManagerSchemaComponentProvider } from './CollectionManagerSchemaComponentProvider';
import { CollectionManagerContext } from './context';
import * as defaultInterfaces from './interfaces';
import { CollectionManagerOptions } from './types';
export const CollectionManagerProvider: React.FC<CollectionManagerOptions> = (props) => {
const { interfaces, collections } = props;
const { service, interfaces, collections = [], refreshCM } = props;
return (
<CollectionManagerContext.Provider value={{ interfaces, collections }}>
<CollectionManagerSchemaComponentProvider>
{props.children}
</CollectionManagerSchemaComponentProvider>
<CollectionManagerContext.Provider
value={{
service,
interfaces: { ...defaultInterfaces, ...interfaces },
collections,
refreshCM: async () => {
if (refreshCM) {
await refreshCM();
}
},
}}
>
<CollectionManagerSchemaComponentProvider>{props.children}</CollectionManagerSchemaComponentProvider>
</CollectionManagerContext.Provider>
);
};
export const RemoteCollectionManagerProvider = (props: any) => {
const api = useAPIClient();
const options = {
resource: 'collections',
action: 'list',
params: {
perPage: 999,
appends: ['fields', 'fields.uiSchema'],
},
};
const service = useRequest(options);
if (service.loading) {
return <Spin />;
}
return (
<CollectionManagerProvider
service={service}
collections={service?.data?.data}
refreshCM={async () => {
const { data } = await api.request(options);
service.mutate(data);
}}
{...props}
/>
);
};

View File

@ -1,23 +1,10 @@
import { DatabaseOutlined } from '@ant-design/icons';
import { ISchema, useForm } from '@formily/react';
import { ISchema } from '@formily/react';
import { uid } from '@formily/shared';
import React, { useState } from 'react';
import { PluginManager } from '../plugin-manager';
import { ActionContext, SchemaComponent, useActionContext } from '../schema-component';
import { ConfigurationTable } from './Configuration';
const useCloseAction = () => {
const { setVisible } = useActionContext();
const form = useForm();
return {
async run() {
setVisible(false);
form.submit((values) => {
console.log(values);
});
},
};
};
import { ActionContext, SchemaComponent } from '../schema-component';
import { AddFieldAction, ConfigurationTable, EditFieldAction } from './Configuration';
const schema: ISchema = {
type: 'object',
@ -46,7 +33,7 @@ export const CollectionManagerShortcut = () => {
setVisible(true);
}}
/>
<SchemaComponent scope={{ useCloseAction }} schema={schema} components={{ ConfigurationTable }} />
<SchemaComponent schema={schema} components={{ ConfigurationTable, AddFieldAction, EditFieldAction }} />
</ActionContext.Provider>
);
};

View File

@ -0,0 +1,134 @@
import { PlusOutlined } from '@ant-design/icons';
import { ISchema } from '@formily/react';
import { uid } from '@formily/shared';
import { Button, Dropdown, Menu } from 'antd';
import React, { useState } from 'react';
import { useAPIClient } from '../../api-client';
import { useRecord } from '../../record-provider';
import { ActionContext, SchemaComponent } from '../../schema-component';
import { useCollectionManager } from '../hooks';
import { IField } from '../interfaces/types';
const getSchema = (schema: IField, useAction): ISchema => {
if (!schema) {
return;
}
return {
type: 'object',
properties: {
[uid()]: {
type: 'void',
'x-component': 'Action.Drawer',
'x-decorator': 'Form',
'x-decorator-props': {
initialValue: {
...schema.default,
name: `f_${uid()}`,
},
},
title: 'Drawer Title',
properties: {
type: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
},
title: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
},
name: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
},
// @ts-ignore
...schema.properties,
footer: {
type: 'void',
'x-component': 'Action.Drawer.Footer',
properties: {
action1: {
title: 'Cancel',
'x-component': 'Action',
'x-component-props': {
useAction: '{{ useCancelAction }}',
},
},
action2: {
title: 'Submit',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
useAction,
},
},
},
},
},
},
},
};
};
export const AddFieldAction = () => {
const { getInterface } = useCollectionManager();
const [visible, setVisible] = useState(false);
const [schema, setSchema] = useState({});
return (
<ActionContext.Provider value={{ visible, setVisible }}>
<Dropdown
overlay={
<Menu
onClick={(info) => {
const schema = getSchema(getInterface(info.key), '{{ useCreateActionAndRefreshCM }}');
setSchema(schema);
setVisible(true);
}}
>
<Menu.SubMenu title={'基本类型'}>
<Menu.Item key={'input'}>input</Menu.Item>
<Menu.Item key={'textarea'}>textarea</Menu.Item>
<Menu.Item key={'chinaRegion'}>China region</Menu.Item>
</Menu.SubMenu>
</Menu>
}
>
<Button icon={<PlusOutlined />} type={'primary'}>
</Button>
</Dropdown>
<SchemaComponent schema={schema} />
</ActionContext.Provider>
);
};
export const EditFieldAction = (props) => {
const record = useRecord();
const { getInterface } = useCollectionManager();
const [visible, setVisible] = useState(false);
const [schema, setSchema] = useState({});
const api = useAPIClient();
return (
<ActionContext.Provider value={{ visible, setVisible }}>
<a
onClick={async () => {
const { data } = await api.resource('collections.fields', record.collectionName).get({
filterByTk: record.name,
appends: ['uiSchema'],
});
const schema = getSchema(
{
...getInterface(record.interface),
default: data?.data,
},
'{{ useUpdateActionAndRefreshCM }}',
);
setSchema(schema);
setVisible(true);
}}
>
</a>
<SchemaComponent schema={schema} />
</ActionContext.Provider>
);
};

View File

@ -5,9 +5,7 @@ import { collectionSchema } from './schemas/collections';
export const ConfigurationTable = () => {
return (
<div>
<SchemaComponent
schema={collectionSchema}
/>
<SchemaComponent schema={collectionSchema} />
</div>
);
};

View File

@ -1 +1,3 @@
export * from './AddFieldAction';
export * from './ConfigurationTable';

View File

@ -20,6 +20,16 @@ const collection = {
required: true,
} as ISchema,
},
{
type: 'string',
name: 'interface',
interface: 'input',
uiSchema: {
title: '字段类型',
type: 'string',
'x-component': 'Input',
},
},
{
type: 'string',
name: 'title',
@ -54,6 +64,7 @@ export const collectionFieldSchema: ISchema = {
sourceKey: 'name',
targetKey: 'name',
},
collection,
request: {
resource: 'collections.fields',
action: 'list',
@ -65,10 +76,10 @@ export const collectionFieldSchema: ISchema = {
},
},
},
'x-component': 'CollectionProvider',
'x-component-props': {
collection,
},
// 'x-component': 'CollectionProvider',
// 'x-component-props': {
// collection,
// },
properties: {
actions: {
type: 'void',
@ -82,57 +93,14 @@ export const collectionFieldSchema: ISchema = {
create: {
type: 'void',
title: '创建',
'x-component': 'Action',
'x-component': 'AddFieldAction',
'x-component-props': {
type: 'primary',
},
properties: {
drawer: {
type: 'void',
'x-component': 'Action.Drawer',
'x-decorator': 'Form',
title: 'Drawer Title',
properties: {
type: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
},
title: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
},
name: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
},
footer: {
type: 'void',
'x-component': 'Action.Drawer.Footer',
properties: {
action1: {
title: 'Cancel',
'x-component': 'Action',
'x-component-props': {
useAction: '{{ useCancelAction }}',
},
},
action2: {
title: 'Submit',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
useAction: '{{ useCreateAction }}',
},
},
},
},
},
},
},
},
},
},
table1: {
table: {
type: 'void',
'x-uid': 'input',
'x-component': 'VoidTable',
@ -169,6 +137,18 @@ export const collectionFieldSchema: ISchema = {
},
},
column3: {
type: 'void',
'x-decorator': 'TableColumnDecorator',
'x-component': 'VoidTable.Column',
properties: {
interface: {
type: 'string',
'x-component': 'CollectionField',
'x-read-pretty': true,
},
},
},
column4: {
type: 'void',
title: 'Actions',
'x-component': 'VoidTable.Column',
@ -183,60 +163,17 @@ export const collectionFieldSchema: ISchema = {
update: {
type: 'void',
title: '编辑',
'x-component': 'Action.Link',
'x-component': 'EditFieldAction',
'x-component-props': {
type: 'primary',
},
properties: {
drawer: {
type: 'void',
'x-component': 'Action.Drawer',
'x-decorator': 'Form',
'x-decorator-props': {
useValues: '{{ useValues }}',
},
title: 'Drawer Title',
properties: {
title: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
},
name: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-disabled': true,
},
footer: {
type: 'void',
'x-component': 'Action.Drawer.Footer',
properties: {
action1: {
title: 'Cancel',
'x-component': 'Action',
'x-component-props': {
useAction: '{{ useCancelAction }}',
},
},
action2: {
title: 'Submit',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
useAction: '{{ useUpdateAction }}',
},
},
},
},
},
},
},
},
delete: {
type: 'void',
title: '删除',
'x-component': 'Action.Link',
'x-component-props': {
useAction: '{{ useDestroyAction }}',
useAction: '{{ useDestroyActionAndRefreshCM }}',
},
},
},

View File

@ -1,9 +1,11 @@
import { ISchema } from '@formily/react';
import { CollectionOptions } from '../../types';
import { collectionFieldSchema } from './collectionFields';
const collection = {
const collection: CollectionOptions = {
name: 'collections',
filterTargetKey: 'name',
targetKey: 'name',
fields: [
{
type: 'integer',
@ -14,7 +16,7 @@ const collection = {
type: 'number',
'x-component': 'Input',
required: true,
} as ISchema,
},
},
{
type: 'string',
@ -25,7 +27,7 @@ const collection = {
type: 'string',
'x-component': 'Input',
description: '使用英文',
} as ISchema,
},
},
{
type: 'hasMany',
@ -47,9 +49,7 @@ export const collectionSchema: ISchema = {
'x-collection': 'collections',
'x-decorator': 'ResourceActionProvider',
'x-decorator-props': {
collection: {
targetKey: 'name',
},
collection,
request: {
resource: 'collections',
action: 'list',
@ -61,10 +61,10 @@ export const collectionSchema: ISchema = {
},
},
},
'x-component': 'CollectionProvider',
'x-component-props': {
collection,
},
// 'x-component': 'CollectionProvider',
// 'x-component-props': {
// collection,
// },
properties: {
actions: {
type: 'void',
@ -113,7 +113,7 @@ export const collectionSchema: ISchema = {
'x-component': 'Action',
'x-component-props': {
type: 'primary',
useAction: '{{ useCreateAction }}',
useAction: '{{ useCreateActionAndRefreshCM }}',
},
},
},
@ -201,7 +201,7 @@ export const collectionSchema: ISchema = {
'x-component': 'Action.Drawer',
'x-decorator': 'Form',
'x-decorator-props': {
useValues: '{{ useValues }}',
useValues: '{{ useValuesFromRecord }}',
},
title: 'Drawer Title',
properties: {
@ -230,7 +230,7 @@ export const collectionSchema: ISchema = {
'x-component': 'Action',
'x-component-props': {
type: 'primary',
useAction: '{{ useUpdateAction }}',
useAction: '{{ useUpdateActionAndRefreshCM }}',
},
},
},
@ -244,7 +244,7 @@ export const collectionSchema: ISchema = {
title: '删除',
'x-component': 'Action.Link',
'x-component-props': {
useAction: '{{ useDestroyAction }}',
useAction: '{{ useDestroyActionAndRefreshCM }}',
},
},
},

View File

@ -1,6 +1,6 @@
import { Result } from 'ahooks/lib/useRequest/src/types';
import React, { createContext, useContext, useEffect } from 'react';
import { useRecord } from '..';
import { CollectionProvider, useRecord } from '..';
import { useAPIClient, useRequest } from '../api-client';
export const ResourceActionContext = createContext<Result<any, any>>(null);
@ -18,18 +18,20 @@ const CollectionResourceActionProvider = (props) => {
const api = useAPIClient();
const service = useRequest(request, {
uid,
refreshDeps: [request],
// refreshDeps: [request],
});
const resource = api.resource(request.resource);
return (
<ResourceContext.Provider value={{ type: 'collection', resource, collection }}>
<ResourceActionContext.Provider value={service}>{props.children}</ResourceActionContext.Provider>
<ResourceActionContext.Provider value={service}>
<CollectionProvider collection={collection}>{props.children}</CollectionProvider>
</ResourceActionContext.Provider>
</ResourceContext.Provider>
);
};
const AssociationResourceActionProvider = (props) => {
const { association, request, uid } = props;
const { collection, association, request, uid } = props;
const api = useAPIClient();
const record = useRecord();
const resourceOf = record[association.sourceKey];
@ -37,13 +39,15 @@ const AssociationResourceActionProvider = (props) => {
{ resourceOf, ...request },
{
uid,
refreshDeps: [request, resourceOf],
// refreshDeps: [request, resourceOf],
},
);
const resource = api.resource(request.resource, resourceOf);
return (
<ResourceContext.Provider value={{ type: 'association', resource, association }}>
<ResourceActionContext.Provider value={service}>{props.children}</ResourceActionContext.Provider>
<ResourceActionContext.Provider value={service}>
<CollectionProvider collection={collection}>{props.children}</CollectionProvider>
</ResourceActionContext.Provider>
</ResourceContext.Provider>
);
};

View File

@ -1,4 +1,5 @@
import { useForm } from '@formily/react';
import { useCollectionManager } from '.';
import { useRequest } from '../api-client';
import { useRecord } from '../record-provider';
import { useActionContext } from '../schema-component';
@ -60,10 +61,43 @@ export const useDestroyAction = () => {
};
};
export const useValues = (options) => {
export const useValuesFromRecord = (options) => {
const record = useRecord();
return useRequest(() => Promise.resolve({ data: record }), {
...options,
refreshDeps: [record],
});
};
export const useCreateActionAndRefreshCM = () => {
const { run } = useCreateAction();
const { refreshCM } = useCollectionManager();
return {
async run() {
await run();
await refreshCM();
},
};
};
export const useUpdateActionAndRefreshCM = () => {
const { run } = useUpdateAction();
const { refreshCM } = useCollectionManager();
return {
async run() {
await run();
await refreshCM();
},
};
};
export const useDestroyActionAndRefreshCM = () => {
const { run } = useDestroyAction();
const { refreshCM } = useCollectionManager();
return {
async run() {
await run();
await refreshCM();
},
};
};

View File

@ -11,7 +11,6 @@ export const useCollectionField = () => {
const ctx = useContext(CollectionFieldContext);
const resourceName = `${ctx?.collectinName || collection?.name}.${ctx.name}`;
const resource = api?.resource(resourceName, record[ctx.sourceKey]);
console.log({ resourceName });
return {
...ctx,
resource,

View File

@ -1,11 +1,19 @@
import { clone } from '@formily/shared';
import { useContext } from 'react';
import { CollectionManagerContext } from '../context';
export const useCollectionManager = () => {
const { collections } = useContext(CollectionManagerContext);
const { refreshCM, service, interfaces, collections } = useContext(CollectionManagerContext);
return {
service,
interfaces,
collections,
refreshCM: () => refreshCM?.(),
get(name: string) {
return collections?.find((collection) => collection.name === name);
},
getInterface(name) {
return interfaces[name] ? clone(interfaces[name]) : null;
},
};
};

View File

@ -0,0 +1,73 @@
import { uid } from '@formily/shared';
import { IField } from './types';
export const chinaRegion: IField = {
name: 'chinaRegion',
type: 'object',
group: 'choices',
order: 7,
title: '{{t("China region")}}',
isAssociation: true,
default: {
interface: 'chinaRegion',
type: 'belongsToMany',
target: 'chinaRegions',
targetKey: 'code',
// name,
uiSchema: {
type: 'array',
// title,
'x-component': 'Cascader',
'x-component-props': {
changeOnSelectLast: false,
labelInValue: true,
maxLevel: 3,
fieldNames: {
label: 'name',
value: 'code',
children: 'children',
},
},
},
},
initialize: (values: any) => {
if (!values.through) {
values.through = `t_${uid()}`;
}
if (!values.foreignKey) {
values.foreignKey = `f_${uid()}`;
}
if (!values.otherKey) {
values.otherKey = `f_${uid()}`;
}
if (!values.sourceKey) {
values.sourceKey = 'id';
}
if (!values.targetKey) {
values.targetKey = 'id';
}
},
properties: {
'uiSchema.x-component-props.maxLevel': {
type: 'number',
'x-component': 'Radio.Group',
'x-decorator': 'FormItem',
title: '{{t("Select level")}}',
default: 3,
enum: [
{ value: 1, label: '{{t("Province")}}' },
{ value: 2, label: '{{t("City")}}' },
{ value: 3, label: '{{t("Area")}}' },
{ value: 4, label: '{{t("Street")}}' },
{ value: 5, label: '{{t("Village")}}' },
],
},
'uiSchema.x-component-props.changeOnSelectLast': {
type: 'boolean',
'x-component': 'Checkbox',
'x-content': '{{t("Must select to the last level")}}',
'x-decorator': 'FormItem',
},
},
operators: [{ label: '{{t("is")}}', value: 'code.in' }],
};

View File

@ -0,0 +1,4 @@
export * from './chinaRegion';
export * from './input';
export * from './textarea';

View File

@ -0,0 +1,28 @@
import { IField } from './types';
export const input: IField = {
name: 'string',
type: 'object',
group: 'basic',
order: 1,
title: '{{t("Single line text")}}',
sortable: true,
default: {
interface: 'input',
type: 'string',
uiSchema: {
type: 'string',
'x-component': 'Input',
},
},
properties: {
},
operators: [
{ label: '{{t("contains")}}', value: '$includes', selected: true },
{ label: '{{t("does not contain")}}', value: '$notIncludes' },
{ label: '{{t("is")}}', value: 'eq' },
{ label: '{{t("is not")}}', value: 'ne' },
{ label: '{{t("is empty")}}', value: '$null', noValue: true },
{ label: '{{t("is not empty")}}', value: '$notNull', noValue: true },
],
};

View File

@ -0,0 +1,198 @@
import { ISchema } from '@formily/react';
export const type: ISchema = {
type: 'string',
title: '{{t("Storage type")}}',
required: true,
'x-disabled': true,
'x-decorator': 'FormItem',
'x-component': 'Select',
enum: [
{ label: 'Boolean', value: 'boolean' },
{ label: 'String', value: 'string' },
{ label: 'Text', value: 'text' },
{ label: 'Integer', value: 'integer' },
{ label: 'Float', value: 'float' },
{ label: 'Decimal', value: 'decimal' },
{ label: 'Date', value: 'date' },
{ label: 'DateOnly', value: 'dateonly' },
{ label: 'Time', value: 'time' },
{ label: 'Virtual', value: 'virtual' },
{ label: 'JSON', value: 'json' },
{ label: 'Password', value: 'password' },
{ label: 'HasOne', value: 'hasOne' },
{ label: 'HasMany', value: 'hasMany' },
{ label: 'BelongsTo', value: 'belongsTo' },
{ label: 'BelongsToMany', value: 'belongsToMany' },
],
};
export const dateTimeProps: { [key: string]: ISchema } = {
'uiSchema.x-component-props.dateFormat': {
type: 'string',
title: '{{t("Date format")}}',
'x-component': 'Radio.Group',
'x-decorator': 'FormItem',
default: 'YYYY-MM-DD',
enum: [
{
label: '{{t("Year/Month/Day")}}',
value: 'YYYY/MM/DD',
},
{
label: '{{t("Year-Month-Day")}}',
value: 'YYYY-MM-DD',
},
{
label: '{{t("Day/Month/Year")}}',
value: 'DD/MM/YYYY',
},
],
},
'uiSchema.x-component-props.showTime': {
type: 'boolean',
'x-decorator': 'FormItem',
'x-component': 'Checkbox',
'x-content': '{{t("Show time")}}',
'x-reactions': [
`{{(field) => {
field.query('..[].timeFormat').take(f => {
f.display = field.value ? 'visible' : 'none';
});
}}}`,
],
},
'uiSchema.x-component-props.timeFormat': {
type: 'string',
title: '{{t("Time format")}}',
'x-component': 'Radio.Group',
'x-decorator': 'FormItem',
default: 'HH:mm:ss',
enum: [
{
label: '{{t("12 hour")}}',
value: 'hh:mm:ss a',
},
{
label: '{{t("24 hour")}}',
value: 'HH:mm:ss',
},
],
},
};
export const dataSource: ISchema = {
type: 'array',
title: '{{t("Options")}}',
'x-decorator': 'FormItem',
'x-component': 'ArrayTable',
'x-component-props': {
pagination: false,
// scroll: { x: '100%' },
},
items: {
type: 'object',
properties: {
column1: {
type: 'void',
'x-component': 'ArrayTable.Column',
'x-component-props': { width: 50, title: '', align: 'center' },
properties: {
sort: {
type: 'void',
'x-component': 'ArrayTable.SortHandle',
},
},
},
column2: {
type: 'void',
'x-component': 'ArrayTable.Column',
'x-component-props': { title: '{{t("Option value")}}' },
'x-hidden': true,
properties: {
value: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
column3: {
type: 'void',
'x-component': 'ArrayTable.Column',
'x-component-props': { title: '{{t("Option label")}}' },
properties: {
label: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
column4: {
type: 'void',
'x-component': 'ArrayTable.Column',
'x-component-props': { title: '{{t("Color")}}' },
properties: {
color: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'ColorSelect',
},
},
},
column5: {
type: 'void',
'x-component': 'ArrayTable.Column',
'x-component-props': {
title: '',
dataIndex: 'operations',
fixed: 'right',
},
properties: {
item: {
type: 'void',
'x-component': 'FormItem',
properties: {
remove: {
type: 'void',
'x-component': 'ArrayTable.Remove',
},
},
},
},
},
},
},
properties: {
add: {
type: 'void',
'x-component': 'ArrayTable.Addition',
'x-component-props': {
randomValue: true,
},
title: "{{t('Add option')}}",
},
},
};
export const defaultProps = {
'uiSchema.title': {
type: 'string',
title: '{{t("Field display name")}}',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
name: {
type: 'string',
title: '{{t("Field name")}}',
required: true,
// 'x-disabled': true,
'x-decorator': 'FormItem',
'x-component': 'Input',
description:
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.')}}",
},
type,
};

View File

@ -0,0 +1,19 @@
import { IField } from './types';
export const textarea: IField = {
name: 'textarea',
type: 'object',
group: 'basic',
order: 2,
title: '{{t("Long text")}}',
default: {
interface: 'textarea',
type: 'text',
// name,
uiSchema: {
type: 'string',
'x-component': 'Input.TextArea',
},
},
properties: {},
};

View File

@ -0,0 +1,12 @@
import { ISchema } from '@formily/react';
interface IDefault {
type: string;
uiSchema?: ISchema;
[key: string]: any;
}
export interface IField extends ISchema {
default?: IDefault;
[key: string]: any;
}

View File

@ -1,14 +1,24 @@
import { ISchema } from '@formily/react';
export interface CollectionManagerOptions {
service?: any;
interfaces?: any;
collections?: any[];
refreshCM?: () => Promise<void>;
}
export interface FieldOptions {
type: string;
interface?: string;
uiSchema?: ISchema;
[key: string]: any;
}
export interface CollectionOptions {
name?: string;
filterTargetKey?: string;
fields?: any[];
targetKey?: string;
fields?: FieldOptions[];
}
export interface ICollectionProviderProps {

View File

@ -1,4 +1,4 @@
import { observer, RecursionField, useField, useFieldSchema } from '@formily/react';
import { observer, useField, useFieldSchema } from '@formily/react';
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import { ActionContext } from './context';
@ -23,7 +23,7 @@ export const ActionLink: ComposedAction = observer((props: any) => {
>
{field.title}
</Link>
<RecursionField basePath={field.address} schema={schema} onlyRenderProperties />
{props.children}
</ActionContext.Provider>
);
});

View File

@ -1,4 +1,4 @@
import { observer, RecursionField, useField, useFieldSchema } from '@formily/react';
import { observer, useField, useFieldSchema } from '@formily/react';
import { Button } from 'antd';
import React, { useState } from 'react';
import ActionContainer from './Action.Container';
@ -28,7 +28,7 @@ export const Action: ComposedAction = observer((props: any) => {
>
{field.title}
</Button>
<RecursionField basePath={field.address} schema={schema} onlyRenderProperties />
{props.children}
</ActionContext.Provider>
);
});

View File

@ -1,58 +0,0 @@
import { observer } from '@formily/react';
import React from 'react';
import { SchemaInitializer } from '../../../schema-initializer';
const useTableColumnInitializerFields = () => {
const fields = [
{
title: 'Field1',
name: 'field1',
component: ArrayTableColumnInitializer,
},
{
title: 'Field2',
name: 'field2',
component: ArrayTableColumnInitializer,
},
].filter((field) => field.component);
return fields;
};
export const TableColumnInitializeButton = observer((props: any) => {
return (
<SchemaInitializer.Button
insertPosition={'beforeEnd'}
items={[
{
type: 'item',
title: 'Display fields',
children: useTableColumnInitializerFields(),
},
]}
>
Configure columns
</SchemaInitializer.Button>
);
});
export const ArrayTableColumnInitializer = SchemaInitializer.itemWrap((props) => {
const { insert } = props;
return (
<SchemaInitializer.Item
onClick={() => {
insert({
type: 'void',
title: 'Name',
'x-component': 'ArrayTable.Column',
properties: {
name: {
type: 'string',
'x-component': 'Input',
'x-read-pretty': true,
},
},
});
}}
/>
);
});

View File

@ -0,0 +1,41 @@
import { observer, useFieldSchema } from '@formily/react';
import React from 'react';
import { createDesignable, useDesignable } from '../..';
import { useAPIClient } from '../../../api-client';
export const TableActionDesignable = observer((props) => {
const fieldSchema = useFieldSchema();
const api = useAPIClient();
const { refresh } = useDesignable();
return (
<div>
<span
onClick={() => {
const spaceSchema = fieldSchema.reduceProperties((buf, schema) => {
if (schema['x-component'] === 'Space') {
return schema;
}
return buf;
}, null);
const dn = createDesignable({
current: spaceSchema,
refresh,
api,
});
dn.loadAPIClientEvents();
dn.insertBeforeEnd({
type: 'void',
title: '{{ t("Delete") }}',
'x-component': 'Action.Link',
'x-component-props': {
useAction: '{{ useDestroyAction }}',
},
});
}}
>
Edit
</span>
{props.children}
</div>
);
});

View File

@ -0,0 +1,4 @@
import React from 'react';
export const TableActionDesignableBar = () => {
return <div>Edit</div>;
};

View File

@ -0,0 +1,14 @@
import { observer, useFieldSchema } from '@formily/react';
import React from 'react';
import { useComponent } from '../..';
export const TableColumnActionBar = observer((props) => {
const fieldSchema = useFieldSchema();
const ActionInitializer = useComponent(fieldSchema['x-action-initializer']);
return (
<div>
{ActionInitializer && <ActionInitializer />}
{props.children}
</div>
);
});

View File

@ -1,4 +1,5 @@
export * from './ArrayTable';
export * from './Initializer';
export * from './TableColumnActionBar';
export * from './TableColumnDecorator';

View File

@ -1,9 +1,18 @@
import React from 'react';
import { useDesignable } from '../..';
import { SortableItem } from '../../common';
export const BlockItem: React.FC<any> = (props) => {
const { remove } = useDesignable();
return (
<SortableItem className={'nb-block-item'} style={{ position: 'relative' }}>
<a
onClick={() => {
remove();
}}
>
</a>
{props.children}
</SortableItem>
);

View File

@ -6,8 +6,6 @@ import cls from 'classnames';
import React, { createContext, useContext, useState } from 'react';
import { useComponent } from '../..';
import { DndContext } from '../../common/dnd-context';
import { AddGridBlockItem } from './AddBlockItem';
import { AddGridFormItem } from './AddFormItem';
const GridRowContext = createContext(null);
const GridColContext = createContext(null);
@ -209,6 +207,3 @@ Grid.Col = observer((props: any) => {
</div>
);
});
Grid.AddFormItem = AddGridFormItem;
Grid.AddBlockItem = AddGridBlockItem;

View File

@ -1,6 +1,15 @@
import { ISchema } from '@formily/react';
import { uid } from '@formily/shared';
import { Form, FormItem, Grid, Input, Markdown, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
import {
Form,
FormItem,
Grid,
Input,
Markdown,
SchemaComponent,
SchemaComponentProvider,
SchemaInitializerProvider
} from '@nocobase/client';
import React from 'react';
const schema: ISchema = {
@ -8,7 +17,7 @@ const schema: ISchema = {
name: 'grid1',
'x-decorator': 'Form',
'x-component': 'Grid',
'x-item-initializer': 'Grid.AddFormItem',
'x-item-initializer': 'AddFormItem',
'x-uid': uid(),
properties: {
row1: {
@ -56,7 +65,9 @@ const schema: ISchema = {
export default function App() {
return (
<SchemaComponentProvider components={{ Markdown, Form, Grid, Input, FormItem }}>
<SchemaComponent schema={schema} />
<SchemaInitializerProvider>
<SchemaComponent schema={schema} />
</SchemaInitializerProvider>
</SchemaComponentProvider>
);
}

View File

@ -9,6 +9,7 @@ import {
Markdown,
SchemaComponent,
SchemaComponentProvider,
SchemaInitializerProvider,
VoidTable
} from '@nocobase/client';
import React from 'react';
@ -29,7 +30,7 @@ const schema: ISchema = {
grid: {
type: 'void',
'x-component': 'Grid',
'x-item-initializer': 'Grid.AddBlockItem',
'x-item-initializer': 'AddBlockItem',
'x-uid': uid(),
properties: {},
},
@ -39,7 +40,9 @@ const schema: ISchema = {
export default function App() {
return (
<SchemaComponentProvider components={{ BlockItem, Block, Grid, CardItem, Markdown, Form, VoidTable }}>
<SchemaComponent schema={schema} />
<SchemaInitializerProvider>
<SchemaComponent schema={schema} />
</SchemaInitializerProvider>
</SchemaComponentProvider>
);
}

View File

@ -475,7 +475,7 @@ export function useDesignable() {
update(key);
refresh();
},
remove(schema: any, options?: RemoveOptions) {
remove(schema?: any, options?: RemoveOptions) {
dn.remove(schema, options);
},
insertAdjacent(position: Position, schema: ISchema, options?: InsertAdjacentOptions) {

View File

@ -0,0 +1,260 @@
import { TableOutlined } from '@ant-design/icons';
import { ISchema } from '@formily/react';
import { clone } from '@formily/shared';
import React from 'react';
import { SchemaInitializer } from '../../';
import { CollectionOptions, useCollectionManager } from '../../../collection-manager';
const collection: CollectionOptions = {
name: 'collections',
filterTargetKey: 'name',
targetKey: 'name',
fields: [
{
type: 'integer',
name: 'title',
interface: 'input',
uiSchema: {
title: '数据表名称',
type: 'number',
'x-component': 'Input',
required: true,
},
},
{
type: 'string',
name: 'name',
interface: 'input',
uiSchema: {
title: '数据表标识',
type: 'string',
'x-component': 'Input',
description: '使用英文',
},
},
{
type: 'hasMany',
name: 'fields',
target: 'fields',
collectionName: 'collections',
sourceKey: 'name',
targetKey: 'name',
uiSchema: {},
},
],
};
export const collectionSchema: ISchema = {
type: 'void',
'x-collection': 'collections',
'x-component': 'CardItem',
'x-decorator': 'ResourceActionProvider',
'x-decorator-props': {
collection,
request: {
resource: 'collections',
action: 'list',
params: {
pageSize: 5,
filter: {},
sort: ['sort'],
appends: [],
},
},
},
properties: {
actions: {
type: 'void',
'x-component': 'ActionBar',
'x-action-initializer': 'AddActionButton',
properties: {
delete: {
type: 'void',
title: '删除',
'x-component': 'Action',
},
create: {
type: 'void',
title: '创建',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
},
properties: {
drawer: {
type: 'void',
'x-component': 'Action.Drawer',
'x-decorator': 'Form',
title: 'Drawer Title',
properties: {
title: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
},
name: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
},
footer: {
type: 'void',
'x-component': 'Action.Drawer.Footer',
properties: {
action1: {
title: 'Cancel',
'x-component': 'Action',
'x-component-props': {
useAction: '{{ useCancelAction }}',
},
},
action2: {
title: 'Submit',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
useAction: '{{ useCreateAction }}',
},
},
},
},
},
},
},
},
},
},
table1: {
type: 'void',
'x-component': 'VoidTable',
'x-component-props': {
rowKey: 'id',
rowSelection: {
type: 'checkbox',
},
useDataSource: '{{ useDataSourceFromRAC }}',
},
'x-column-initializer': 'AddTableColumn',
properties: {
column3: {
type: 'void',
title: 'Actions',
'x-component': 'VoidTable.Column',
properties: {
actions: {
type: 'void',
'x-component': 'Space',
'x-component-props': {
split: '|',
},
properties: {
view: {
type: 'void',
title: '配置字段',
'x-component': 'Action.Link',
'x-component-props': {},
properties: {
drawer: {
type: 'void',
'x-component': 'Action.Drawer',
title: 'Drawer Title',
properties: {},
},
},
},
update: {
type: 'void',
title: '编辑',
'x-component': 'Action.Link',
'x-component-props': {
type: 'primary',
},
properties: {
drawer: {
type: 'void',
'x-component': 'Action.Drawer',
'x-decorator': 'Form',
'x-decorator-props': {
useValues: '{{ useValues }}',
},
title: 'Drawer Title',
properties: {
title: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
},
name: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-disabled': true,
},
footer: {
type: 'void',
'x-component': 'Action.Drawer.Footer',
properties: {
action1: {
title: 'Cancel',
'x-component': 'Action',
'x-component-props': {
useAction: '{{ useCancelAction }}',
},
},
action2: {
title: 'Submit',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
useAction: '{{ useUpdateAction }}',
},
},
},
},
},
},
},
},
delete: {
type: 'void',
title: '删除',
'x-component': 'Action.Link',
'x-component-props': {
useAction: '{{ useDestroyAction }}',
},
},
},
},
},
},
},
},
},
};
const itemWrap = SchemaInitializer.itemWrap;
export const FormBlockInitializerItem = itemWrap((props) => {
const { insert } = props;
const { collections } = useCollectionManager();
return (
<SchemaInitializer.Item
icon={<TableOutlined />}
onClick={() => {
insert(clone(collectionSchema));
}}
items={[
{
type: 'itemGroup',
title: 'select a data source',
children: collections?.map((item) => {
return {
type: 'item',
name: item.name,
title: item.title,
};
}),
},
]}
>
Form
</SchemaInitializer.Item>
);
});

View File

@ -0,0 +1,135 @@
import { TableOutlined } from '@ant-design/icons';
import { ISchema } from '@formily/react';
import { clone } from '@formily/shared';
import React from 'react';
import { SchemaInitializer } from '../..';
import { CollectionOptions, useCollectionManager } from '../../../collection-manager';
const collection: CollectionOptions = {
name: 'collections',
filterTargetKey: 'name',
targetKey: 'name',
fields: [
{
type: 'integer',
name: 'title',
interface: 'input',
uiSchema: {
title: '数据表名称',
type: 'number',
required: true,
'x-component': 'Input',
},
},
{
type: 'string',
name: 'name',
interface: 'input',
uiSchema: {
title: '数据表标识',
type: 'string',
description: '使用英文',
'x-component': 'Input',
},
},
{
type: 'hasMany',
name: 'fields',
target: 'fields',
collectionName: 'collections',
sourceKey: 'name',
targetKey: 'name',
uiSchema: {},
},
],
};
export const collectionSchema: ISchema = {
type: 'void',
'x-collection': 'collections',
'x-component': 'CardItem',
'x-decorator': 'ResourceActionProvider',
'x-decorator-props': {
collection,
request: {
resource: 'collections',
action: 'list',
params: {
pageSize: 5,
filter: {},
sort: ['sort'],
appends: [],
},
},
},
properties: {
actions: {
type: 'void',
'x-component': 'ActionBar',
'x-action-initializer': 'TableActionInitializer',
properties: {},
},
table1: {
type: 'void',
'x-component': 'VoidTable',
'x-component-props': {
rowKey: 'id',
rowSelection: {
type: 'checkbox',
},
useDataSource: '{{ useDataSourceFromRAC }}',
},
'x-column-initializer': 'TableColumnInitializer',
properties: {
actions: {
type: 'void',
title: 'Actions',
'x-decorator': 'TableColumnActionBar',
'x-component': 'VoidTable.Column',
'x-action-initializer': 'TableColumnActionInitializer',
properties: {
actions: {
type: 'void',
'x-component': 'Space',
'x-component-props': {
split: '|',
},
properties: {},
},
},
},
},
},
},
};
const itemWrap = SchemaInitializer.itemWrap;
export const TableBlockInitializerItem = itemWrap((props) => {
const { insert } = props;
const { collections } = useCollectionManager();
return (
<SchemaInitializer.Item
icon={<TableOutlined />}
onClick={() => {
insert(clone(collectionSchema));
}}
items={[
{
type: 'itemGroup',
title: 'select a data source',
children: collections?.map((item) => {
return {
type: 'item',
name: item.name,
title: item.title,
};
}),
},
]}
>
Table
</SchemaInitializer.Item>
);
});

View File

@ -1,8 +1,10 @@
import { FormOutlined, TableOutlined } from '@ant-design/icons';
import { FormOutlined } from '@ant-design/icons';
import { ISchema, observer } from '@formily/react';
import { uid } from '@formily/shared';
import React from 'react';
import { SchemaInitializer } from '../../../schema-initializer';
import { SchemaInitializer } from '../..';
import { FormBlockInitializerItem } from './FormBlockInitializerItem';
import { TableBlockInitializerItem } from './TableBlockInitializerItem';
const gridRowColWrap = (schema: ISchema) => {
return {
@ -22,87 +24,6 @@ const gridRowColWrap = (schema: ISchema) => {
const itemWrap = SchemaInitializer.itemWrap;
const TableBlockInitializerItem = itemWrap((props) => {
const { insert } = props;
return (
<SchemaInitializer.Item
icon={<TableOutlined />}
onClick={() => {
insert({
type: 'void',
'x-component': 'VoidTable',
'x-decorator': 'CardItem',
});
}}
items={[
{
type: 'itemGroup',
title: 'select a data source',
children: [
{
type: 'item',
name: 'users',
title: 'Users',
},
{
type: 'item',
name: 'posts',
title: 'Posts',
},
],
},
]}
>
Table
</SchemaInitializer.Item>
);
});
const FormBlockInitializerItem = itemWrap((props) => {
const { insert } = props;
return (
<SchemaInitializer.Item
icon={<FormOutlined />}
onClick={() => {
insert({
type: 'void',
'x-uid': uid(),
'x-component': 'Form',
'x-decorator': 'CardItem',
properties: {
[uid()]: {
type: 'void',
'x-component': 'Grid',
'x-item-initializer': 'Grid.AddFormItem',
'x-uid': uid(),
},
},
});
}}
items={[
{
type: 'itemGroup',
title: 'select a data source',
children: [
{
type: 'item',
name: 'users',
title: 'Users',
},
{
type: 'item',
name: 'posts',
title: 'Posts',
},
],
},
]}
>
Form
</SchemaInitializer.Item>
);
});
const TestInitializerItem = itemWrap((props) => {
const { insert } = props;
return (
@ -193,7 +114,7 @@ const TestInitializerItem = itemWrap((props) => {
);
});
export const AddGridBlockItem = observer((props: any) => {
export const BlockInitializer = observer((props: any) => {
return (
<SchemaInitializer.Button
wrap={gridRowColWrap}
@ -213,11 +134,6 @@ export const AddGridBlockItem = observer((props: any) => {
title: 'Form',
component: FormBlockInitializerItem,
},
{
type: 'item',
title: 'Test',
component: TestInitializerItem,
},
],
},
]}

View File

@ -0,0 +1,84 @@
import { observer, useFieldSchema } from '@formily/react';
import { Switch } from 'antd';
import React from 'react';
import { SchemaInitializer } from '../..';
import { useDesignable } from '../../../schema-component';
const useCurrentActionSchema = (action: string) => {
const fieldSchema = useFieldSchema();
const { remove } = useDesignable();
const schema = fieldSchema.reduceProperties((buf, s) => {
if (s['x-action'] === action) {
return s;
}
return buf;
});
return {
schema,
exists: !!schema,
remove() {
schema && remove(schema);
},
};
};
const InitializeAction = SchemaInitializer.itemWrap((props) => {
const { item, insert } = props;
const { exists, remove } = useCurrentActionSchema(item.schema['x-action']);
return (
<SchemaInitializer.Item
onClick={() => {
if (exists) {
return remove();
}
insert({
type: 'void',
'x-component': 'Action',
...item.schema,
});
}}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
{item.title} <Switch size={'small'} checked={exists} />
</div>
</SchemaInitializer.Item>
);
});
export const DetailsActionInitializer = observer((props: any) => {
return (
<SchemaInitializer.Button
insertPosition={'beforeEnd'}
style={{ marginLeft: 8 }}
items={[
{
type: 'itemGroup',
title: 'Enable actions',
children: [
{
type: 'item',
title: 'Create',
component: InitializeAction,
schema: {
title: 'Create',
'x-action': 'posts:create',
'x-align': 'left',
},
},
{
type: 'item',
title: 'Update',
component: InitializeAction,
schema: {
title: 'Update',
'x-action': 'posts:update',
},
},
],
},
]}
>
Configure actions
</SchemaInitializer.Button>
);
});

View File

@ -2,8 +2,8 @@ import { ISchema, observer, Schema, useFieldSchema } from '@formily/react';
import { uid } from '@formily/shared';
import { Switch } from 'antd';
import React from 'react';
import { SchemaInitializer, SchemaInitializerItemOptions } from '../../../schema-initializer';
import { useDesignable } from '../../hooks';
import { SchemaInitializer, SchemaInitializerItemOptions } from '../..';
import { useDesignable } from '../../../schema-component';
const useFormItemInitializerFields = () => {
return [
@ -122,7 +122,7 @@ const InitializeTextFormItem = itemWrap((props) => {
);
});
export const AddGridFormItem = observer((props: any) => {
export const FormItemInitializer = observer((props: any) => {
return (
<SchemaInitializer.Button
wrap={gridRowColWrap}

View File

@ -0,0 +1,142 @@
import { FormOutlined } from '@ant-design/icons';
import { ISchema, observer } from '@formily/react';
import { uid } from '@formily/shared';
import React from 'react';
import { SchemaInitializer } from '../..';
const gridRowColWrap = (schema: ISchema) => {
return {
type: 'void',
'x-component': 'Grid.Row',
properties: {
[uid()]: {
type: 'void',
'x-component': 'Grid.Col',
properties: {
[schema.name || uid()]: schema,
},
},
},
};
};
const itemWrap = SchemaInitializer.itemWrap;
const TestInitializerItem = itemWrap((props) => {
const { insert } = props;
return (
<SchemaInitializer.Item
icon={<FormOutlined />}
onClick={() => {
insert({
type: 'void',
name: uid(),
'x-decorator': 'CardItem',
'x-component': 'Grid',
'x-uid': uid(),
properties: {
row1: {
type: 'void',
'x-component': 'Grid.Row',
'x-uid': uid(),
properties: {
col11: {
type: 'void',
'x-component': 'Grid.Col',
properties: {
block1: {
type: 'void',
title: '1',
'x-decorator': 'BlockItem',
'x-component': 'Block',
},
block2: {
type: 'void',
title: '2',
'x-decorator': 'BlockItem',
'x-component': 'Block',
},
},
},
col12: {
type: 'void',
'x-component': 'Grid.Col',
properties: {
block3: {
type: 'void',
title: '3',
'x-decorator': 'BlockItem',
'x-component': 'Block',
},
},
},
},
},
row2: {
type: 'void',
'x-component': 'Grid.Row',
'x-uid': uid(),
properties: {
col21: {
type: 'void',
'x-component': 'Grid.Col',
properties: {
block4: {
type: 'void',
title: '4',
'x-decorator': 'BlockItem',
'x-component': 'Block',
},
},
},
col22: {
type: 'void',
'x-component': 'Grid.Col',
properties: {
block5: {
type: 'void',
title: '5',
'x-decorator': 'BlockItem',
'x-component': 'Block',
},
},
},
},
},
},
});
}}
>
Test
</SchemaInitializer.Item>
);
});
export const RecordBlockInitializer = observer((props: any) => {
return (
<SchemaInitializer.Button
wrap={gridRowColWrap}
insertPosition={'beforeEnd'}
items={[
{
type: 'itemGroup',
title: 'Data blocks',
children: [
{
type: 'item',
title: 'Form',
component: TestInitializerItem,
},
{
type: 'item',
title: 'Details',
component: TestInitializerItem,
},
],
},
]}
>
Add block
</SchemaInitializer.Button>
);
});

View File

@ -0,0 +1,96 @@
import { observer, useFieldSchema } from '@formily/react';
import { Switch } from 'antd';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { SchemaInitializer } from '../..';
import { useDesignable } from '../../../schema-component';
const useCurrentActionSchema = (action: string) => {
const fieldSchema = useFieldSchema();
const { remove } = useDesignable();
const schema = fieldSchema.reduceProperties((buf, s) => {
if (s['x-action'] === action) {
return s;
}
return buf;
});
return {
schema,
exists: !!schema,
remove() {
schema && remove(schema);
},
};
};
const InitializeAction = SchemaInitializer.itemWrap((props) => {
const { item, insert } = props;
const { exists, remove } = useCurrentActionSchema(item.schema['x-action']);
return (
<SchemaInitializer.Item
onClick={() => {
if (exists) {
return remove();
}
insert({
type: 'void',
'x-component': 'Action',
...item.schema,
});
}}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
{item.title}
<Switch style={{ marginLeft: 20 }} size={'small'} checked={exists} />
</div>
</SchemaInitializer.Item>
);
});
export const TableActionInitializer = observer((props: any) => {
const { t } = useTranslation();
return (
<SchemaInitializer.Button
insertPosition={'beforeEnd'}
style={{ marginLeft: 8 }}
items={[
{
type: 'itemGroup',
title: t('Enable actions'),
children: [
{
type: 'item',
title: t('Filter'),
component: InitializeAction,
schema: {
title: '{{ t("Filter") }}',
'x-action': 'filter',
'x-align': 'left',
},
},
{
type: 'item',
title: t('Add new'),
component: InitializeAction,
schema: {
title: '{{ t("Add new") }}',
'x-action': 'create',
},
},
{
type: 'item',
title: t('Delete'),
component: InitializeAction,
schema: {
title: '{{ t("Delete") }}',
'x-action': 'destroy',
},
},
],
},
]}
>
{t('Configure actions')}
</SchemaInitializer.Button>
);
});

View File

@ -0,0 +1,145 @@
import { observer, Schema, useFieldSchema } from '@formily/react';
import { Switch } from 'antd';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { SchemaInitializer } from '../..';
import { useAPIClient } from '../../../api-client';
import { createDesignable, useDesignable } from '../../../schema-component';
const useCurrentActionSchema = (action: string) => {
const fieldSchema = useFieldSchema();
const { remove } = useDesignable();
const findActionSchema = (schema: Schema) => {
return schema.reduceProperties((buf, s) => {
if (s['x-action'] === action) {
return s;
}
const c = findActionSchema(s);
if (c) {
return c;
}
return buf;
});
};
const schema = findActionSchema(fieldSchema);
return {
schema,
exists: !!schema,
remove() {
schema && remove(schema);
},
};
};
const InitializeAction = SchemaInitializer.itemWrap((props) => {
const { item, insert } = props;
const { exists, remove } = useCurrentActionSchema(item.schema['x-action']);
return (
<SchemaInitializer.Item
onClick={() => {
console.log('InitializeAction', insert);
if (exists) {
return remove();
}
insert({
type: 'void',
'x-component': 'Action.Link',
...item.schema,
});
}}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
{item.title} <Switch style={{ marginLeft: 20 }} size={'small'} checked={exists} />
</div>
</SchemaInitializer.Item>
);
});
export const TableColumnActionInitializer = observer((props: any) => {
const fieldSchema = useFieldSchema();
const api = useAPIClient();
const { refresh } = useDesignable();
const { t } = useTranslation();
return (
<SchemaInitializer.Button
insertPosition={'beforeEnd'}
insert={(schema) => {
const spaceSchema = fieldSchema.reduceProperties((buf, schema) => {
if (schema['x-component'] === 'Space') {
return schema;
}
return buf;
}, null);
if (!spaceSchema) {
return;
}
const dn = createDesignable({
api,
refresh,
current: spaceSchema,
});
dn.loadAPIClientEvents();
dn.insertBeforeEnd(schema);
}}
items={[
{
type: 'itemGroup',
title: t('Enable actions'),
children: [
{
type: 'item',
title: t('View'),
component: InitializeAction,
schema: {
title: '{{ t("View") }}',
type: 'void',
'x-action': 'view',
'x-component': 'Action.Link',
'x-component-props': {},
properties: {
drawer: {
type: 'void',
'x-component': 'Action.Drawer',
title: '{{ t("View record") }}',
properties: {},
},
},
},
},
{
type: 'item',
title: t('Edit'),
component: InitializeAction,
schema: {
title: '{{ t("Edit") }}',
type: 'void',
'x-action': 'update',
'x-component': 'Action.Link',
'x-component-props': {},
properties: {
drawer: {
type: 'void',
'x-component': 'Action.Drawer',
title: '{{ t("Edit record") }}',
properties: {},
},
},
}
},
{
type: 'item',
title: t('Delete'),
component: InitializeAction,
schema: {
title: '{{ t("Delete") }}',
'x-action': 'destroy',
},
},
],
},
]}
>
Configure
</SchemaInitializer.Button>
);
});

View File

@ -0,0 +1,99 @@
import { observer, Schema, useFieldSchema } from '@formily/react';
import { Switch } from 'antd';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { SchemaInitializer, SchemaInitializerItemOptions } from '../..';
import { useCollection, useDesignable } from '../../..';
const useTableColumnInitializerFields = () => {
const { name, fields } = useCollection();
return fields
.filter((field) => field?.uiSchema?.title)
.map((field) => {
return {
type: 'item',
title: field?.uiSchema?.title,
schema: {
name: field.name,
'x-collection-field': `${name}.${field.name}`,
'x-component': 'CollectionField',
},
component: ColumnInitializerItem,
} as SchemaInitializerItemOptions;
});
};
const useCurrentColumnSchema = (path: string) => {
const fieldSchema = useFieldSchema();
const { remove } = useDesignable();
const findFieldSchema = (schema: Schema) => {
return schema.reduceProperties((buf, s) => {
if (s['x-collection-field'] === path) {
return s;
}
const child = findFieldSchema(s);
if (child) {
return child;
}
return buf;
});
};
const schema = findFieldSchema(fieldSchema);
return {
schema,
exists: !!schema,
remove() {
schema && remove(schema.parent);
},
};
};
const ColumnInitializerItem = SchemaInitializer.itemWrap((props) => {
const { item, insert } = props;
const { exists, remove } = useCurrentColumnSchema(item.schema['x-collection-field']);
return (
<SchemaInitializer.Item
onClick={() => {
if (exists) {
return remove();
}
insert({
type: 'void',
'x-decorator': 'TableColumnDecorator',
'x-component': 'VoidTable.Column',
properties: {
[item.schema.name]: {
'x-read-pretty': true,
...item.schema,
},
},
});
}}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
{item.title} <Switch style={{ marginLeft: 20 }} size={'small'} checked={exists} />
</div>
</SchemaInitializer.Item>
);
});
/**
*
*/
export const TableColumnInitializer = observer((props: any) => {
const { t } = useTranslation();
return (
<SchemaInitializer.Button
insertPosition={'beforeEnd'}
items={[
{
type: 'itemGroup',
title: t('Display fields'),
children: useTableColumnInitializerFields(),
},
]}
>
{t('Configure columns')}
</SchemaInitializer.Button>
);
});

View File

@ -0,0 +1,8 @@
export * from './BlockInitializer';
export * from './DetailsActionInitializer';
export * from './FormItemInitializer';
export * from './RecordBlockInitializer';
export * from './TableActionInitializer';
export * from './TableColumnActionInitializer';
export * from './TableColumnInitializer';

View File

@ -16,10 +16,16 @@ export const SchemaInitializerItemContext = createContext(null);
export const SchemaInitializer = () => null;
SchemaInitializer.Button = observer((props: SchemaInitializerButtonProps) => {
const { wrap = defaultWrap, items = [], insertPosition, dropdown, ...others } = props;
const { insertAdjacent, findComponent } = useDesignable();
const { insert, wrap = defaultWrap, items = [], insertPosition, dropdown, ...others } = props;
let { insertAdjacent, findComponent } = useDesignable();
const [visible, setVisible] = useState(false);
const insertSchema = (schema) => {
if (props.insert) {
props.insert(wrap(schema));
} else {
insertAdjacent(insertPosition, wrap(schema));
}
};
const menu = (
<Menu>
{items?.map((item, indexA) => {
@ -36,18 +42,10 @@ SchemaInitializer.Button = observer((props: SchemaInitializerButtonProps) => {
index: indexA,
item,
info: item,
insert: (schema) => {
insertAdjacent(insertPosition, wrap(schema));
},
insert: insertSchema,
}}
>
<Component
{...item}
item={item}
insert={(schema) => {
insertAdjacent(insertPosition, wrap(schema));
}}
/>
<Component {...item} item={item} insert={insertSchema} />
</SchemaInitializerItemContext.Provider>
)
);
@ -68,18 +66,10 @@ SchemaInitializer.Button = observer((props: SchemaInitializerButtonProps) => {
index: indexB,
item: child,
info: child,
insert: (schema) => {
insertAdjacent(insertPosition, wrap(schema));
},
insert: insertSchema,
}}
>
<Component
{...child}
item={child}
insert={(schema) => {
insertAdjacent(insertPosition, wrap(schema));
}}
/>
<Component {...child} item={child} insert={insertSchema} />
</SchemaInitializerItemContext.Provider>
)
);
@ -91,6 +81,8 @@ SchemaInitializer.Button = observer((props: SchemaInitializerButtonProps) => {
</Menu>
);
console.log('others', others);
return (
<Dropdown
visible={visible}

View File

@ -0,0 +1,7 @@
import React from 'react';
import { SchemaComponentOptions } from '../schema-component';
import * as initializers from './Initializers';
export const SchemaInitializerProvider: React.FC = (props) => {
return <SchemaComponentOptions components={initializers}>{props.children}</SchemaComponentOptions>;
};

View File

@ -1,3 +1,4 @@
export * from './SchemaInitializer';
export * from './SchemaInitializerProvider';
export * from './types';

View File

@ -2,6 +2,7 @@ import { ISchema } from '@formily/react';
import { ButtonProps, DropDownProps, MenuItemProps } from 'antd';
export interface SchemaInitializerButtonProps extends ButtonProps {
insert?: (s: ISchema) => void;
wrap?: (s: ISchema) => ISchema;
insertPosition?: 'beforeBegin' | 'afterBegin' | 'beforeEnd' | 'afterEnd';
items?: SchemaInitializerItemOptions[];

View File

@ -54,12 +54,12 @@ export class UiRoutesStoragePlugin extends Plugin {
grid1: {
type: 'void',
'x-component': 'Grid',
'x-item-initializer': 'Grid.AddBlockItem',
'x-item-initializer': 'BlockInitializer',
properties: {},
},
},
}
}
},
},
},
item8: {
type: 'void',