refactor(client): schema initializer

This commit is contained in:
chenos 2022-02-22 11:17:24 +08:00
parent d486768eda
commit f45b08af38
51 changed files with 1108 additions and 1492 deletions

View File

@ -10,6 +10,7 @@ import {
DesignableSwitch, DesignableSwitch,
DocumentTitleProvider, DocumentTitleProvider,
i18n, i18n,
MenuItemInitializers,
PluginManagerProvider, PluginManagerProvider,
RemoteCollectionManagerProvider, RemoteCollectionManagerProvider,
RouteSchemaComponent, RouteSchemaComponent,
@ -50,7 +51,7 @@ const providers = [
], ],
[SchemaComponentProvider, { components: { Link, NavLink } }], [SchemaComponentProvider, { components: { Link, NavLink } }],
RemoteCollectionManagerProvider, RemoteCollectionManagerProvider,
SchemaInitializerProvider, [SchemaInitializerProvider, { initializers: { MenuItemInitializers } }],
AntdSchemaComponentProvider, AntdSchemaComponentProvider,
[DocumentTitleProvider, { addonAfter: 'NocoBase' }], [DocumentTitleProvider, { addonAfter: 'NocoBase' }],
[ [

View File

@ -125,7 +125,7 @@ export default (apiClient: APIClient) => {
type: 'void', type: 'void',
name: 'grid1', name: 'grid1',
'x-component': 'Grid', 'x-component': 'Grid',
'x-item-initializer': 'Grid.AddBlockItem', 'x-initializer': 'Grid.AddBlockItem',
'x-uid': uid(), 'x-uid': uid(),
properties: {}, properties: {},
}, },
@ -152,7 +152,7 @@ export default (apiClient: APIClient) => {
type: 'void', type: 'void',
name: 'grid1', name: 'grid1',
'x-component': 'Grid', 'x-component': 'Grid',
'x-item-initializer': 'Grid.AddBlockItem', 'x-initializer': 'Grid.AddBlockItem',
'x-uid': uid(), 'x-uid': uid(),
properties: {}, properties: {},
}, },

View File

@ -1,18 +1,18 @@
import { observer, RecursionField, useFieldSchema } from '@formily/react'; import { observer, RecursionField, useFieldSchema } from '@formily/react';
import { Space } from 'antd'; import { Space } from 'antd';
import React from 'react'; import React from 'react';
import { useSchemaInitializer } from '../../../schema-initializer';
import { DndContext } from '../../common'; import { DndContext } from '../../common';
import { useComponent } from '../../hooks';
export const ActionBar = observer((props: any) => { export const ActionBar = observer((props: any) => {
const { layout = 'tow-columns', style, ...others } = props; const { layout = 'tow-columns', style, ...others } = props;
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const ActionInitializer = useComponent(fieldSchema['x-action-initializer']); const { render } = useSchemaInitializer(fieldSchema['x-initializer']);
if (layout === 'one-column') { if (layout === 'one-column') {
return ( return (
<div style={{ display: 'flex', ...style }} {...others}> <div style={{ display: 'flex', ...style }} {...others}>
{props.children && <div style={{ marginRight: 8 }}>{props.children}</div>} {props.children && <div style={{ marginRight: 8 }}>{props.children}</div>}
{ActionInitializer && <ActionInitializer />} {render()}
</div> </div>
); );
} }
@ -38,7 +38,7 @@ export const ActionBar = observer((props: any) => {
</Space> </Space>
</DndContext> </DndContext>
</div> </div>
{ActionInitializer && <ActionInitializer />} {render()}
</div> </div>
); );
}); });

View File

@ -5,8 +5,7 @@ import { Table, TableColumnProps } from 'antd';
import cls from 'classnames'; import cls from 'classnames';
import React from 'react'; import React from 'react';
import { DndContext } from '../..'; import { DndContext } from '../..';
import { RecordProvider } from '../../../'; import { RecordProvider, useSchemaInitializer } from '../../../';
import { useComponent } from '../../hooks';
const isColumnComponent = (schema: Schema) => { const isColumnComponent = (schema: Schema) => {
return schema['x-component']?.endsWith('.Column') > -1; return schema['x-component']?.endsWith('.Column') > -1;
@ -15,7 +14,7 @@ const isColumnComponent = (schema: Schema) => {
const useTableColumns = () => { const useTableColumns = () => {
const field = useField<ArrayField>(); const field = useField<ArrayField>();
const schema = useFieldSchema(); const schema = useFieldSchema();
const Initializer = useComponent(schema['x-column-initializer']); const { exists, render } = useSchemaInitializer(schema['x-initializer']);
const columns = schema const columns = schema
.reduceProperties((buf, s) => { .reduceProperties((buf, s) => {
if (isColumnComponent(s)) { if (isColumnComponent(s)) {
@ -38,11 +37,11 @@ const useTableColumns = () => {
} as TableColumnProps<any>; } as TableColumnProps<any>;
}); });
console.log(columns); console.log(columns);
if (!Initializer) { if (!exists) {
return columns; return columns;
} }
return columns.concat({ return columns.concat({
title: <Initializer />, title: render(),
dataIndex: 'TABLE_COLUMN_INITIALIZER', dataIndex: 'TABLE_COLUMN_INITIALIZER',
key: 'TABLE_COLUMN_INITIALIZER', key: 'TABLE_COLUMN_INITIALIZER',
}); });

View File

@ -12,7 +12,7 @@ export const useColumnSchema = () => {
return buf; return buf;
}, null); }, null);
if (!fieldSchema) { if (!fieldSchema) {
return; return {};
} }
const collectionField = getField(fieldSchema.name); const collectionField = getField(fieldSchema.name);
return { columnSchema, fieldSchema, collectionField }; return { columnSchema, fieldSchema, collectionField };
@ -21,7 +21,7 @@ export const useColumnSchema = () => {
export const TableColumnDecorator = (props) => { export const TableColumnDecorator = (props) => {
const Designer = useDesigner(); const Designer = useDesigner();
const field = useField(); const field = useField();
const { columnSchema, fieldSchema, collectionField } = useColumnSchema(); const { fieldSchema, collectionField } = useColumnSchema();
const { refresh } = useDesignable(); const { refresh } = useDesignable();
useLayoutEffect(() => { useLayoutEffect(() => {
if (field.title) { if (field.title) {

View File

@ -1,12 +1,14 @@
import { DragOutlined } from '@ant-design/icons'; import { DragOutlined } from '@ant-design/icons';
import { useFieldSchema } from '@formily/react'; import { useFieldSchema } from '@formily/react';
import { useSchemaInitializer } from '@nocobase/client';
import { Space } from 'antd'; import { Space } from 'antd';
import React from 'react'; import React from 'react';
import { DragHandler, useComponent } from '../../../schema-component'; import { DragHandler } from '../../../schema-component';
export const TableRecordActionDesigner = (props: any) => { export const TableRecordActionDesigner = (props: any) => {
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const ActionInitializer = useComponent(fieldSchema['x-action-initializer']); const { render } = useSchemaInitializer(fieldSchema['x-initializer']);
console.log("fieldSchema['x-initializer']", fieldSchema['x-initializer'])
return ( return (
<div className={'general-schema-designer'}> <div className={'general-schema-designer'}>
<div className={'general-schema-designer-icons'}> <div className={'general-schema-designer-icons'}>
@ -14,7 +16,7 @@ export const TableRecordActionDesigner = (props: any) => {
<DragHandler> <DragHandler>
<DragOutlined /> <DragOutlined />
</DragHandler> </DragHandler>
<ActionInitializer /> {render()}
</Space> </Space>
</div> </div>
</div> </div>

View File

@ -4,7 +4,7 @@ import { observer, RecursionField, Schema, useField, useFieldSchema } from '@for
import { uid } from '@formily/shared'; import { uid } from '@formily/shared';
import cls from 'classnames'; import cls from 'classnames';
import React, { createContext, useContext, useState } from 'react'; import React, { createContext, useContext, useState } from 'react';
import { useComponent } from '../..'; import { useSchemaInitializer } from '../../../';
import { DndContext } from '../../common/dnd-context'; import { DndContext } from '../../common/dnd-context';
const GridRowContext = createContext(null); const GridRowContext = createContext(null);
@ -123,7 +123,7 @@ const useColProperties = () => {
export const Grid: any = observer((props) => { export const Grid: any = observer((props) => {
const field = useField(); const field = useField();
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const ItemInitializer = useComponent(fieldSchema['x-item-initializer']); const { render } = useSchemaInitializer(fieldSchema['x-initializer']);
const addr = field.address.toString(); const addr = field.address.toString();
const rows = useRowProperties(); const rows = useRowProperties();
return ( return (
@ -145,7 +145,7 @@ export const Grid: any = observer((props) => {
); );
})} })}
</DndContext> </DndContext>
<div>{ItemInitializer && <ItemInitializer />}</div> <div>{render()}</div>
</div> </div>
); );
}); });

View File

@ -10,8 +10,8 @@ import {
import { Menu as AntdMenu } from 'antd'; import { Menu as AntdMenu } from 'antd';
import React, { createContext, useContext, useEffect, useState } from 'react'; import React, { createContext, useContext, useEffect, useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { createDesignable, DndContext, SortableItem, useComponent, useDesignable, useDesigner } from '../..'; import { createDesignable, DndContext, SortableItem, useDesignable, useDesigner } from '../..';
import { Icon, useAPIClient } from '../../../'; import { Icon, useAPIClient, useSchemaInitializer } from '../../../';
import { MenuDesigner } from './Menu.Designer'; import { MenuDesigner } from './Menu.Designer';
import { findKeysByUid, findMenuItem } from './util'; import { findKeysByUid, findMenuItem } from './util';
@ -146,7 +146,7 @@ export const Menu: ComposedMenu = observer((props) => {
const schema = useFieldSchema(); const schema = useFieldSchema();
const { refresh } = useDesignable(); const { refresh } = useDesignable();
const api = useAPIClient(); const api = useAPIClient();
const Initializer = useComponent(schema['x-initializer']); const { render } = useSchemaInitializer(schema['x-initializer']);
const sideMenuRef = useSideMenuRef(); const sideMenuRef = useSideMenuRef();
const [defaultSelectedKeys, setDefaultSelectedKeys] = useState(() => { const [defaultSelectedKeys, setDefaultSelectedKeys] = useState(() => {
if (dSelectedKeys) { if (dSelectedKeys) {
@ -241,7 +241,7 @@ export const Menu: ComposedMenu = observer((props) => {
defaultSelectedKeys={defaultSelectedKeys} defaultSelectedKeys={defaultSelectedKeys}
> >
<RecursionField schema={schema} onlyRenderProperties /> <RecursionField schema={schema} onlyRenderProperties />
{Initializer && <Initializer style={{ background: 'none', marginTop: 7, marginLeft: 8 }} />} {render({ style: { background: 'none', marginTop: 7, marginLeft: 8 } })}
</AntdMenu> </AntdMenu>
{loading {loading
? null ? null
@ -285,21 +285,19 @@ export const Menu: ComposedMenu = observer((props) => {
`} `}
> >
<RecursionField schema={sideMenuSchema} onlyRenderProperties /> <RecursionField schema={sideMenuSchema} onlyRenderProperties />
{Initializer && ( {render({
<Initializer style: { margin: 8 },
style={{ margin: 8 }} insert: (s) => {
insert={(s) => { console.log('createDesignable', s);
console.log('createDesignable', s); const dn = createDesignable({
const dn = createDesignable({ api,
api, refresh,
refresh, current: sideMenuSchema,
current: sideMenuSchema, });
}); dn.loadAPIClientEvents();
dn.loadAPIClientEvents(); dn.insertAdjacent('beforeEnd', s);
dn.insertAdjacent('beforeEnd', s); },
}} })}
/>
)}
</AntdMenu> </AntdMenu>
</MenuModeContext.Provider>, </MenuModeContext.Provider>,
sideMenuRef.current.firstChild, sideMenuRef.current.firstChild,
@ -331,9 +329,15 @@ Menu.URL = observer((props) => {
const field = useField(); const field = useField();
const Designer = useContext(MenuItemDesignerContext); const Designer = useContext(MenuItemDesignerContext);
return ( return (
<AntdMenu.Item {...others} key={schema.name} eventKey={schema.name} schema={schema} onClick={() => { <AntdMenu.Item
window.open(props.href, '_blank'); {...others}
}}> key={schema.name}
eventKey={schema.name}
schema={schema}
onClick={() => {
window.open(props.href, '_blank');
}}
>
<SortableItem className={designerCss}> <SortableItem className={designerCss}>
<Icon style={{ marginRight: 5 }} type={icon} /> <Icon style={{ marginRight: 5 }} type={icon} />
{schema.title} {schema.title}

View File

@ -1,11 +1,11 @@
import { FormDialog, FormLayout } from '@formily/antd'; import { FormDialog, FormLayout } from '@formily/antd';
import { observer, SchemaOptionsContext } from '@formily/react'; import { SchemaOptionsContext } from '@formily/react';
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { SchemaComponent, SchemaComponentOptions } from '../../..'; import { SchemaComponent, SchemaComponentOptions } from '../../..';
import { SchemaInitializer } from '../../../../schema-initializer'; import { SchemaInitializer } from '../../../../schema-initializer';
export const MenuItemInitializer = observer((props: any) => { export const MenuItemInitializers = (props: any) => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<SchemaInitializer.Button <SchemaInitializer.Button
@ -34,7 +34,7 @@ export const MenuItemInitializer = observer((props: any) => {
{t('Add menu item')} {t('Add menu item')}
</SchemaInitializer.Button> </SchemaInitializer.Button>
); );
}); };
const itemWrap = SchemaInitializer.itemWrap; const itemWrap = SchemaInitializer.itemWrap;
@ -137,7 +137,7 @@ export const PageMenuItem = itemWrap((props) => {
grid: { grid: {
type: 'void', type: 'void',
'x-component': 'Grid', 'x-component': 'Grid',
'x-item-initializer': 'BlockInitializer', 'x-initializer': 'BlockInitializers',
properties: {}, properties: {},
}, },
}, },

View File

@ -1,4 +1,4 @@
export * from './Menu'; export * from './Menu';
export * from './MenuItemInitializer'; export * from './MenuItemInitializers';
export * from './util'; export * from './util';

View File

@ -1,71 +0,0 @@
import { ISchema, observer } from '@formily/react';
import { uid } from '@formily/shared';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { SchemaInitializer } from '../..';
import { CalendarBlock } from './CalendarBlock';
import { FormBlock } from './FormBlock';
import { MarkdownBlock } from './MarkdownBlock';
import { TableBlock } from './TableBlock';
const gridRowColWrap = (schema: ISchema) => {
return {
type: 'void',
'x-component': 'Grid.Row',
properties: {
[uid()]: {
type: 'void',
'x-component': 'Grid.Col',
properties: {
[schema.name || uid()]: schema,
},
},
},
};
};
export const BlockInitializer = observer((props: any) => {
const { t } = useTranslation();
return (
<SchemaInitializer.Button
wrap={gridRowColWrap}
insertPosition={'beforeEnd'}
items={[
{
type: 'itemGroup',
title: t('Data blocks'),
children: [
{
type: 'item',
title: t('Table'),
component: TableBlock,
},
{
type: 'item',
title: t('Form'),
component: FormBlock,
},
{
type: 'item',
title: t('Calendar'),
component: CalendarBlock,
},
],
},
{
type: 'itemGroup',
title: t('Media'),
children: [
{
type: 'item',
title: t('Markdown'),
component: MarkdownBlock,
},
],
},
]}
>
{t('Add block')}
</SchemaInitializer.Button>
);
});

View File

@ -0,0 +1,41 @@
import { gridRowColWrap } from './utils';
// 页面里添加区块
export const BlockInitializers = {
title: '{{t("Add block")}}',
wrap: gridRowColWrap,
items: [
{
type: 'itemGroup',
title: 'Data blocks',
children: [
{
type: 'item',
title: 'Table',
component: 'TableBlockInitializer',
},
{
type: 'item',
title: 'Form',
component: 'FormBlockInitializer',
},
{
type: 'item',
title: 'Calendar',
component: 'CalendarBlockInitializer',
},
],
},
{
type: 'itemGroup',
title: 'Media',
children: [
{
type: 'item',
title: 'Markdown',
component: 'MarkdownBlockInitializer',
},
],
},
],
};

View File

@ -1,187 +0,0 @@
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-designer': 'Action.Designer',
'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 CalendarActionInitializer = 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('Today'),
component: InitializeAction,
schema: {
title: t('Today'),
'x-component': 'Calendar.Today',
'x-action': `calendar:today`,
'x-align': 'left',
},
},
{
type: 'item',
title: t('Turn page'),
component: InitializeAction,
schema: {
title: t('Turn page'),
'x-component': 'Calendar.Nav',
'x-action': `calendar:nav`,
'x-align': 'left',
},
},
{
type: 'item',
title: t('Title'),
component: InitializeAction,
schema: {
title: t('Title'),
'x-component': 'Calendar.Title',
'x-action': `calendar:title`,
'x-align': 'left',
},
},
{
type: 'item',
title: t('Select view'),
component: InitializeAction,
schema: {
title: t('Select view'),
'x-component': 'Calendar.ViewSelect',
'x-action': `calendar:viewSelect`,
'x-align': 'left',
},
},
{
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: {
type: 'void',
title: '{{ t("Add new") }}',
'x-action': 'create',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
openMode: 'drawer',
},
properties: {
drawer: {
type: 'void',
title: '{{ t("Add new record") }}',
'x-component': 'Action.Container',
'x-component-props': {},
'x-decorator': 'Form',
properties: {
grid: {
type: 'void',
'x-component': 'Grid',
'x-item-initializer': 'FormItemInitializer',
properties: {},
},
footer: {
type: 'void',
'x-component': 'Action.Container.Footer',
properties: {
actions: {
type: 'void',
'x-action-initializer': 'PopupFormActionInitializer',
'x-decorator': 'DndContext',
'x-component': 'ActionBar',
'x-component-props': {
layout: 'one-column',
},
properties: {},
},
},
},
},
},
},
},
},
{
type: 'item',
title: t('Delete'),
component: InitializeAction,
schema: {
title: '{{ t("Delete") }}',
'x-action': 'destroy',
'x-component-props': {
confirm: {
title: "{{t('Delete record')}}",
content: "{{t('Are you sure you want to delete it?')}}",
},
useAction: '{{ cm.useBulkDestroyAction }}',
},
},
},
],
},
]}
>
{t('Configure actions')}
</SchemaInitializer.Button>
);
});

View File

@ -0,0 +1,75 @@
// 日历的操作配置
export const CalendarActionInitializers = {
title: 'Configure actions',
style: { marginLeft: 8 },
items: [
{
type: 'itemGroup',
title: 'Enable actions',
children: [
{
type: 'item',
title: 'Today',
component: 'ActionInitializer',
schema: {
title: 'Today',
'x-component': 'Calendar.Today',
'x-action': `calendar:today`,
'x-align': 'left',
},
},
{
type: 'item',
title: 'Turn pages',
component: 'ActionInitializer',
schema: {
title: 'Turn page',
'x-component': 'Calendar.Nav',
'x-action': `calendar:nav`,
'x-align': 'left',
},
},
{
type: 'item',
title: 'Title',
component: 'ActionInitializer',
schema: {
title: 'Title',
'x-component': 'Calendar.Title',
'x-action': `calendar:title`,
'x-align': 'left',
},
},
{
type: 'item',
title: 'Select view',
component: 'ActionInitializer',
schema: {
title: 'Select view',
'x-component': 'Calendar.ViewSelect',
'x-action': `calendar:viewSelect`,
'x-align': 'right',
'x-designer': 'Action.Designer',
},
},
{
type: 'item',
title: 'Filter',
component: 'ActionInitializer',
schema: {
title: '{{ t("Filter") }}',
'x-action': 'filter',
'x-align': 'right',
'x-component': 'Action',
'x-designer': 'Action.Designer',
},
},
{
type: 'item',
title: '{{ t("Add new") }}',
component: 'AddNewActionInitializer',
},
],
},
],
};

View File

@ -1,84 +0,0 @@
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-designer': 'TestDesigner',
'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: 'Edit',
component: InitializeAction,
schema: {
title: 'Edit',
'x-action': 'update',
},
},
{
type: 'item',
title: 'Delete',
component: InitializeAction,
schema: {
title: 'Delete',
'x-action': 'delete',
},
},
],
},
]}
>
Configure actions
</SchemaInitializer.Button>
);
});

View File

@ -0,0 +1,30 @@
// 详情的操作配置
export const DetailsActionInitializers = {
title: 'Configure actions',
items: [
{
type: 'itemGroup',
title: 'Enable actions',
children: [
{
type: 'item',
title: 'Edit',
component: 'ActionInitializer',
schema: {
title: 'Edit',
'x-action': 'update',
},
},
{
type: 'item',
title: 'Delete',
component: 'ActionInitializer',
schema: {
title: 'Delete',
'x-action': 'delete',
},
},
],
},
],
};

View File

@ -1,83 +0,0 @@
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-designer': 'Action.Designer',
'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 FormActionInitializer = observer((props: any) => {
const { t } = useTranslation();
return (
<SchemaInitializer.Button
insertPosition={'beforeEnd'}
items={[
{
type: 'itemGroup',
title: t('Enable actions'),
children: [
{
type: 'item',
title: t('Submit'),
component: InitializeAction,
schema: {
title: '{{ t("Submit") }}',
'x-action': 'submit',
'x-align': 'left',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
useAction: '{{ cm.useCreateActionWithoutRefresh }}',
},
},
},
],
},
]}
>
{t('Configure actions')}
</SchemaInitializer.Button>
);
});

View File

@ -0,0 +1,27 @@
// 普通表单的操作配置
export const FormActionInitializers = {
title: 'Configure actions',
items: [
{
type: 'itemGroup',
title: 'Enable actions',
children: [
{
type: 'item',
title: '{{ t("Submit") }}',
component: 'ActionInitializer',
schema: {
title: '{{ t("Submit") }}',
'x-action': 'submit',
'x-align': 'left',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
useAction: '{{ cm.useCreateActionWithoutRefresh }}',
},
},
},
],
},
],
};

View File

@ -1,145 +0,0 @@
import { ISchema, observer, Schema, useFieldSchema } from '@formily/react';
import { uid } from '@formily/shared';
import { Switch } from 'antd';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { SchemaInitializer, SchemaInitializerItemOptions } from '../..';
import { useCollection } from '../../../collection-manager';
import { useDesignable } from '../../../schema-component';
const useFormItemInitializerFields = () => {
const { name, fields } = useCollection();
return fields?.map((field) => {
return {
type: 'item',
title: field?.uiSchema?.title || field.name,
component: FormItem,
schema: {
name: field.name,
'x-designer': 'FormItem.Designer',
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-collection-field': `${name}.${field.name}`,
},
} as SchemaInitializerItemOptions;
});
};
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 useCurrentFieldSchema = (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, {
removeParentsIfNoChildren: true,
breakRemoveOn: (s) => {
return s['x-component'] === 'Grid';
},
});
},
};
};
const itemWrap = SchemaInitializer.itemWrap;
const FormItem = itemWrap((props) => {
const { item, insert } = props;
const { schema, exists, remove } = useCurrentFieldSchema(item.schema['x-collection-field']);
return (
<SchemaInitializer.Item
onClick={() => {
if (exists) {
return remove();
}
insert({
...item.schema,
});
}}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
{item.title} <Switch size={'small'} checked={exists} />
</div>
</SchemaInitializer.Item>
);
});
const TextItem = itemWrap((props) => {
const { insert } = props;
const { t } = useTranslation();
return (
<SchemaInitializer.Item
onClick={() => {
insert({
type: 'void',
'x-editable': false,
'x-decorator': 'FormItem',
'x-designer': 'Markdown.Void.Designer',
'x-component': 'Markdown.Void',
'x-component-props': {
content: t('This is a demo text, **supports Markdown syntax**.'),
},
});
}}
/>
);
});
export const FormItemInitializer = observer((props: any) => {
const { t } = useTranslation();
return (
<SchemaInitializer.Button
wrap={gridRowColWrap}
insertPosition={'beforeEnd'}
items={[
{
type: 'itemGroup',
title: t('Display fields'),
children: useFormItemInitializerFields(),
},
{
type: 'divider',
},
{
type: 'item',
title: t('Add text'),
component: TextItem,
},
]}
>
{t('Configure fields')}
</SchemaInitializer.Button>
);
});

View File

@ -0,0 +1,41 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { SchemaInitializer } from '../SchemaInitializer';
import { gridRowColWrap, useFormItemInitializerFields } from './utils';
// Grid 组件里配置字段
export const GridFormItemInitializers = (props: any) => {
const { t } = useTranslation();
return (
<SchemaInitializer.Button
wrap={gridRowColWrap}
items={[
{
type: 'itemGroup',
title: 'Display fields',
children: useFormItemInitializerFields(),
},
{
type: 'divider',
},
{
type: 'item',
title: 'Add text',
component: 'GeneralInitializer',
schema: {
type: 'void',
'x-editable': false,
'x-decorator': 'FormItem',
'x-designer': 'Markdown.Void.Designer',
'x-component': 'Markdown.Void',
'x-component-props': {
content: t('This is a demo text, **supports Markdown syntax**.'),
},
},
},
]}
>
{t('Configure fields')}
</SchemaInitializer.Button>
);
};

View File

@ -0,0 +1,26 @@
import { Switch } from 'antd';
import React from 'react';
import { SchemaInitializer } from '../../SchemaInitializer';
import { useCurrentSchema } from '../utils';
export const ActionInitializer = (props) => {
const { item, insert } = props;
const { exists, remove } = useCurrentSchema(item.schema['x-action'], 'x-action', item.find);
return (
<SchemaInitializer.Item
onClick={() => {
if (exists) {
return remove();
}
insert({
...item.schema,
});
}}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
{item.title} <Switch style={{ marginLeft: 20 }} size={'small'} checked={exists} />
</div>
</SchemaInitializer.Item>
);
};

View File

@ -0,0 +1,67 @@
import { Switch } from 'antd';
import React from 'react';
import { SchemaInitializer } from '../../SchemaInitializer';
import { useCurrentSchema } from '../utils';
export const AddNewActionInitializer = (props) => {
const { item, insert } = props;
const { exists, remove } = useCurrentSchema(item?.schema?.['x-action'] || 'create', 'x-action', item.find);
return (
<SchemaInitializer.Item
onClick={() => {
if (exists) {
return remove();
}
insert({
type: 'void',
title: '{{ t("Add new") }}',
'x-action': 'create',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
openMode: 'drawer',
},
properties: {
drawer: {
type: 'void',
title: '{{ t("Add new record") }}',
'x-component': 'Action.Container',
'x-component-props': {},
'x-decorator': 'Form',
properties: {
grid: {
type: 'void',
'x-component': 'Grid',
'x-initializer': 'GridFormItemInitializers',
properties: {},
},
footer: {
type: 'void',
'x-component': 'Action.Container.Footer',
properties: {
actions: {
type: 'void',
'x-initializer': 'PopupFormActionInitializers',
'x-decorator': 'DndContext',
'x-component': 'ActionBar',
'x-component-props': {
layout: 'one-column',
},
properties: {},
},
},
},
},
},
},
...item.schema,
});
}}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
{item.title} <Switch style={{ marginLeft: 20 }} size={'small'} checked={exists} />
</div>
</SchemaInitializer.Item>
);
};

View File

@ -3,9 +3,9 @@ import { FormDialog, FormLayout } from '@formily/antd';
import { ISchema, SchemaOptionsContext } from '@formily/react'; import { ISchema, SchemaOptionsContext } from '@formily/react';
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { SchemaInitializer } from '../..';
import { useCollectionManager } from '../../../collection-manager'; import { useCollectionManager } from '../../../collection-manager';
import { SchemaComponent, SchemaComponentOptions } from '../../../schema-component'; import { SchemaComponent, SchemaComponentOptions } from '../../../schema-component';
import { SchemaInitializer } from '../../SchemaInitializer';
const createSchema = (collectionName, { title, start, end }) => { const createSchema = (collectionName, { title, start, end }) => {
const schema: ISchema = { const schema: ISchema = {
@ -38,7 +38,12 @@ const createSchema = (collectionName, { title, start, end }) => {
toolBar: { toolBar: {
type: 'void', type: 'void',
'x-component': 'Calendar.ActionBar', 'x-component': 'Calendar.ActionBar',
'x-action-initializer': 'CalendarActionInitializer', 'x-component-props': {
style: {
marginBottom: 24,
},
},
'x-initializer': 'CalendarActionInitializers',
properties: {}, properties: {},
}, },
event: { event: {
@ -58,7 +63,7 @@ const createSchema = (collectionName, { title, start, end }) => {
grid: { grid: {
type: 'void', type: 'void',
'x-component': 'Grid', 'x-component': 'Grid',
'x-item-initializer': 'FormItemInitializer', 'x-initializer': 'GridFormItemInitializers',
properties: {}, properties: {},
}, },
footer: { footer: {
@ -67,7 +72,7 @@ const createSchema = (collectionName, { title, start, end }) => {
properties: { properties: {
actions: { actions: {
type: 'void', type: 'void',
'x-action-initializer': 'PopupFormActionInitializer', 'x-initializer': 'PopupFormActionInitializers',
'x-decorator': 'DndContext', 'x-decorator': 'DndContext',
'x-component': 'ActionBar', 'x-component': 'ActionBar',
'x-component-props': { 'x-component-props': {
@ -88,9 +93,7 @@ const createSchema = (collectionName, { title, start, end }) => {
return schema; return schema;
}; };
const itemWrap = SchemaInitializer.itemWrap; export const CalendarBlockInitializer = (props) => {
export const CalendarBlock = itemWrap((props) => {
const { insert } = props; const { insert } = props;
const { collections, getCollection } = useCollectionManager(); const { collections, getCollection } = useCollectionManager();
const { t } = useTranslation(); const { t } = useTranslation();
@ -160,4 +163,4 @@ export const CalendarBlock = itemWrap((props) => {
]} ]}
/> />
); );
}); };

View File

@ -0,0 +1,31 @@
import { Switch } from 'antd';
import React from 'react';
import { SchemaInitializer } from '../../SchemaInitializer';
import { useCurrentSchema } from '../utils';
export const CollectionFieldInitializer = (props) => {
const { item, insert } = props;
const { exists, remove } = useCurrentSchema(
item.schema['x-collection-field'],
'x-collection-field',
item.find,
item.remove,
);
return (
<SchemaInitializer.Item
onClick={() => {
console.log(item, exists);
if (exists) {
return remove();
}
insert({
...item.schema,
});
}}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
{item.title} <Switch style={{ marginLeft: 20 }} size={'small'} checked={exists} />
</div>
</SchemaInitializer.Item>
);
};

View File

@ -29,12 +29,12 @@ const createSchema = (collectionName) => {
grid: { grid: {
type: 'void', type: 'void',
'x-component': 'Grid', 'x-component': 'Grid',
'x-item-initializer': 'FormItemInitializer', 'x-initializer': 'GridFormItemInitializers',
properties: {}, properties: {},
}, },
actions: { actions: {
type: 'void', type: 'void',
'x-action-initializer': 'FormActionInitializer', 'x-initializer': 'FormActionInitializers',
'x-component': 'ActionBar', 'x-component': 'ActionBar',
'x-component-props': { 'x-component-props': {
layout: 'one-column', layout: 'one-column',
@ -51,9 +51,7 @@ const createSchema = (collectionName) => {
return schema; return schema;
}; };
const itemWrap = SchemaInitializer.itemWrap; export const FormBlockInitializer = (props) => {
export const FormBlock = itemWrap((props) => {
const { insert } = props; const { insert } = props;
const { collections } = useCollectionManager(); const { collections } = useCollectionManager();
const { t } = useTranslation(); const { t } = useTranslation();
@ -79,4 +77,4 @@ export const FormBlock = itemWrap((props) => {
]} ]}
/> />
); );
}); };

View File

@ -0,0 +1,15 @@
import React from 'react';
import { SchemaInitializer } from '../../SchemaInitializer';
export const GeneralInitializer = (props) => {
const { item, insert } = props;
return (
<SchemaInitializer.Item
onClick={() => {
insert({
...item.schema,
});
}}
/>
);
};

View File

@ -3,9 +3,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { SchemaInitializer } from '../..'; import { SchemaInitializer } from '../..';
const itemWrap = SchemaInitializer.itemWrap; export const MarkdownBlockInitializer = (props) => {
export const MarkdownBlock = itemWrap((props) => {
const { insert } = props; const { insert } = props;
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
@ -26,4 +24,4 @@ export const MarkdownBlock = itemWrap((props) => {
}} }}
/> />
); );
}); };

View File

@ -28,7 +28,7 @@ const createSchema = (collectionName) => {
properties: { properties: {
actions: { actions: {
type: 'void', type: 'void',
'x-action-initializer': 'TableActionInitializer', 'x-initializer': 'TableActionInitializers',
'x-component': 'ActionBar', 'x-component': 'ActionBar',
'x-component-props': { 'x-component-props': {
style: { style: {
@ -47,7 +47,7 @@ const createSchema = (collectionName) => {
}, },
useDataSource: '{{ cm.useDataSourceFromRAC }}', useDataSource: '{{ cm.useDataSourceFromRAC }}',
}, },
'x-column-initializer': 'TableColumnInitializer', 'x-initializer': 'TableColumnInitializers',
properties: { properties: {
actions: { actions: {
type: 'void', type: 'void',
@ -55,7 +55,7 @@ const createSchema = (collectionName) => {
'x-decorator': 'TableColumnActionBar', 'x-decorator': 'TableColumnActionBar',
'x-component': 'VoidTable.Column', 'x-component': 'VoidTable.Column',
'x-designer': 'TableRecordActionDesigner', 'x-designer': 'TableRecordActionDesigner',
'x-action-initializer': 'TableRecordActionInitializer', 'x-initializer': 'TableRecordActionInitializers',
properties: { properties: {
actions: { actions: {
type: 'void', type: 'void',
@ -75,9 +75,7 @@ const createSchema = (collectionName) => {
return schema; return schema;
}; };
const itemWrap = SchemaInitializer.itemWrap; export const TableBlockInitializer = (props) => {
export const TableBlock = itemWrap((props) => {
const { insert } = props; const { insert } = props;
const { collections } = useCollectionManager(); const { collections } = useCollectionManager();
const { t } = useTranslation(); const { t } = useTranslation();
@ -103,4 +101,4 @@ export const TableBlock = itemWrap((props) => {
]} ]}
/> />
); );
}); };

View File

@ -0,0 +1,9 @@
export * from './ActionInitializer';
export * from './AddNewActionInitializer';
export * from './CalendarBlockInitializer';
export * from './CollectionFieldInitializer';
export * from './FormBlockInitializer';
export * from './GeneralInitializer';
export * from './MarkdownBlockInitializer';
export * from './TableBlockInitializer';

View File

@ -1,95 +0,0 @@
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-designer': 'Action.Designer',
'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 PopupFormActionInitializer = observer((props: any) => {
const { t } = useTranslation();
return (
<SchemaInitializer.Button
insertPosition={'beforeEnd'}
items={[
{
type: 'itemGroup',
title: t('Enable actions'),
children: [
{
type: 'item',
title: t('Submit'),
component: InitializeAction,
schema: {
title: '{{ t("Submit") }}',
'x-action': 'submit',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
useAction: '{{ cm.useCreateAction }}',
},
},
},
{
type: 'item',
title: t('Cancel'),
component: InitializeAction,
schema: {
title: '{{ t("Cancel") }}',
'x-action': 'cancel',
'x-component': 'Action',
'x-component-props': {
useAction: '{{ cm.useCancelAction }}',
},
},
},
],
},
]}
>
{t('Configure actions')}
</SchemaInitializer.Button>
);
});

View File

@ -0,0 +1,39 @@
// 弹窗表单的操作配置
export const PopupFormActionInitializers = {
title: "{{t('Configure actions')}}",
items: [
{
type: 'itemGroup',
title: "{{t('Enable actions')}}",
children: [
{
type: 'item',
title: "{{t('Submit')}}",
component: 'ActionInitializer',
schema: {
title: '{{ t("Submit") }}',
'x-action': 'submit',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
useAction: '{{ cm.useCreateAction }}',
},
},
},
{
type: 'item',
title: "{{t('Cancel')}}",
component: 'ActionInitializer',
schema: {
title: '{{ t("Cancel") }}',
'x-action': 'cancel',
'x-component': 'Action',
'x-component-props': {
useAction: '{{ cm.useCancelAction }}',
},
},
},
],
},
],
};

View File

@ -1,64 +0,0 @@
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 FormBlock = itemWrap((props) => {
const { insert } = props;
return (
<SchemaInitializer.Item
icon={<FormOutlined />}
onClick={() => {
insert({});
}}
/>
);
});
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: FormBlock,
},
{
type: 'item',
title: 'Details',
component: FormBlock,
},
],
},
]}
>
Add block
</SchemaInitializer.Button>
);
});

View File

@ -0,0 +1,8 @@
import { gridRowColWrap } from './utils';
// 当前行记录所在面板的添加区块
export const RecordBlockInitializers = {
wrap: gridRowColWrap,
title: "{{t('Add block')}}",
items: [],
};

View File

@ -1,143 +0,0 @@
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-designer': 'Action.Designer',
'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: {
type: 'void',
title: '{{ t("Add new") }}',
'x-action': 'create',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
openMode: 'drawer',
},
properties: {
drawer: {
type: 'void',
title: '{{ t("Add new record") }}',
'x-component': 'Action.Container',
'x-component-props': {},
'x-decorator': 'Form',
properties: {
grid: {
type: 'void',
'x-component': 'Grid',
'x-item-initializer': 'FormItemInitializer',
properties: {},
},
footer: {
type: 'void',
'x-component': 'Action.Container.Footer',
properties: {
actions: {
type: 'void',
'x-action-initializer': 'PopupFormActionInitializer',
'x-decorator': 'DndContext',
'x-component': 'ActionBar',
'x-component-props': {
layout: 'one-column',
},
properties: {},
},
},
},
},
},
},
},
},
{
type: 'item',
title: t('Delete'),
component: InitializeAction,
schema: {
title: '{{ t("Delete") }}',
'x-action': 'destroy',
'x-component-props': {
confirm: {
title: "{{t('Delete record')}}",
content: "{{t('Are you sure you want to delete it?')}}",
},
useAction: '{{ cm.useBulkDestroyAction }}',
},
},
},
],
},
]}
>
{t('Configure actions')}
</SchemaInitializer.Button>
);
});

View File

@ -0,0 +1,50 @@
// 表格操作配置
export const TableActionInitializers = {
title: "{{t('Configure actions')}}",
style: {
marginLeft: 8,
},
items: [
{
type: 'itemGroup',
title: "{{t('Enable actions')}}",
children: [
{
type: 'item',
title: "{{t('Filter')}}",
component: 'ActionInitializer',
schema: {
title: '{{ t("Filter") }}',
'x-action': 'filter',
'x-align': 'left',
'x-component': 'Action',
'x-designer': 'Action.Designer',
},
},
{
type: 'item',
title: "{{t('Add new')}}",
component: 'AddNewActionInitializer',
},
{
type: 'item',
title: "{{t('Delete')}}",
component: 'ActionInitializer',
schema: {
title: '{{ t("Delete") }}',
'x-action': 'destroy',
'x-component': 'Action',
'x-designer': 'Action.Designer',
'x-component-props': {
confirm: {
title: "{{t('Delete record')}}",
content: "{{t('Are you sure you want to delete it?')}}",
},
useAction: '{{ cm.useBulkDestroyAction }}',
},
},
},
],
},
],
};

View File

@ -1,102 +0,0 @@
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 || field.name,
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-designer': 'TableColumnDeigner',
'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,37 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { SchemaInitializer } from '../SchemaInitializer';
import { useTableColumnInitializerFields } from './utils';
// 表格列配置
export const TableColumnInitializers = (props: any) => {
const { t } = useTranslation();
return (
<SchemaInitializer.Button
insertPosition={'beforeEnd'}
wrap={(s) => {
return {
type: 'void',
'x-decorator': 'TableColumnDecorator',
'x-designer': 'TableColumnDeigner',
'x-component': 'VoidTable.Column',
properties: {
[s.name]: {
'x-read-pretty': true,
...s,
},
},
};
}}
items={[
{
type: 'itemGroup',
title: t('Display fields'),
children: useTableColumnInitializerFields(),
},
]}
>
{t('Configure columns')}
</SchemaInitializer.Button>
);
};

View File

@ -1,92 +1,18 @@
import { MenuOutlined } from '@ant-design/icons'; import { MenuOutlined } from '@ant-design/icons';
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import { observer, Schema, useFieldSchema } from '@formily/react'; import { useFieldSchema } from '@formily/react';
import { Switch } from 'antd';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { SchemaInitializer } from '../..'; import { SchemaInitializer } from '../..';
import { useAPIClient } from '../../../api-client'; import { useAPIClient } from '../../api-client';
import { createDesignable, useDesignable } from '../../../schema-component'; import { createDesignable, useDesignable } from '../../schema-component';
const useCurrentActionSchema = (action: string) => { export const TableRecordActionInitializers = (props: any) => {
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 InitializeViewAction = 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>
);
});
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-designer': 'Action.Designer',
'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 TableRecordActionInitializer = observer((props: any) => {
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const api = useAPIClient(); const api = useAPIClient();
const { refresh } = useDesignable(); const { refresh } = useDesignable();
const { t } = useTranslation(); const { t } = useTranslation();
console.log('TableRecordActionInitializers');
return ( return (
<SchemaInitializer.Button <SchemaInitializer.Button
className={css` className={css`
@ -126,7 +52,7 @@ export const TableRecordActionInitializer = observer((props: any) => {
{ {
type: 'item', type: 'item',
title: t('View'), title: t('View'),
component: InitializeViewAction, component: 'ActionInitializer',
schema: { schema: {
title: '{{ t("View") }}', title: '{{ t("View") }}',
type: 'void', type: 'void',
@ -171,11 +97,12 @@ export const TableRecordActionInitializer = observer((props: any) => {
{ {
type: 'item', type: 'item',
title: t('Edit'), title: t('Edit'),
component: InitializeAction, component: 'ActionInitializer',
schema: { schema: {
title: '{{ t("Edit") }}', title: '{{ t("Edit") }}',
type: 'void', type: 'void',
'x-action': 'update', 'x-action': 'update',
'x-designer': 'Action.Designer',
'x-component': 'Action.Link', 'x-component': 'Action.Link',
'x-component-props': {}, 'x-component-props': {},
properties: { properties: {
@ -218,10 +145,12 @@ export const TableRecordActionInitializer = observer((props: any) => {
{ {
type: 'item', type: 'item',
title: t('Delete'), title: t('Delete'),
component: InitializeAction, component: 'ActionInitializer',
schema: { schema: {
title: '{{ t("Delete") }}', title: '{{ t("Delete") }}',
'x-action': 'destroy', 'x-action': 'destroy',
'x-designer': 'Action.Designer',
'x-component': 'Action.Link',
'x-component-props': { 'x-component-props': {
confirm: { confirm: {
title: "{{t('Delete record')}}", title: "{{t('Delete record')}}",
@ -238,4 +167,4 @@ export const TableRecordActionInitializer = observer((props: any) => {
<MenuOutlined /> <MenuOutlined />
</SchemaInitializer.Button> </SchemaInitializer.Button>
); );
}); };

View File

@ -1,11 +0,0 @@
export * from './BlockInitializer';
export * from './CalendarActionInitializer';
export * from './DetailsActionInitializer';
export * from './FormActionInitializer';
export * from './FormItemInitializer';
export * from './PopupFormActionInitializer';
export * from './RecordBlockInitializer';
export * from './TableActionInitializer';
export * from './TableColumnInitializer';
export * from './TableRecordActionInitializer';

View File

@ -0,0 +1,36 @@
import { BlockInitializers } from './BlockInitializers';
import { CalendarActionInitializers } from './CalendarActionInitializers';
import { DetailsActionInitializers } from './DetailsActionInitializers';
import { FormActionInitializers } from './FormActionInitializers';
import { GridFormItemInitializers } from './GridFormItemInitializers';
import * as Items from './Items';
import { PopupFormActionInitializers } from './PopupFormActionInitializers';
import { RecordBlockInitializers } from './RecordBlockInitializers';
import { TableActionInitializers } from './TableActionInitializers';
import { TableColumnInitializers } from './TableColumnInitializers';
import { TableRecordActionInitializers } from './TableRecordActionInitializers';
export const items = { ...Items };
export const initializes = {
// 页面里的「添加区块」
BlockInitializers,
// 日历的「操作配置」
CalendarActionInitializers,
// 详情的「操作配置」
DetailsActionInitializers,
// 普通表单的「操作配置」
FormActionInitializers,
// Grid 组件里「配置字段」
GridFormItemInitializers,
// 弹窗表单的「操作配置」
PopupFormActionInitializers,
// 当前行记录所在面板的「添加区块」
RecordBlockInitializers,
// 表格「操作配置」
TableActionInitializers,
// 表格「列配置」
TableColumnInitializers,
// 表格当前行记录的「操作配置」
TableRecordActionInitializers,
};

View File

@ -0,0 +1,109 @@
import { ISchema, Schema, useFieldSchema } from '@formily/react';
import { uid } from '@formily/shared';
import { SchemaInitializerItemOptions } from '../';
import { useCollection } from '../../collection-manager';
import { useDesignable } from '../../schema-component';
export const gridRowColWrap = (schema: ISchema) => {
return {
type: 'void',
'x-component': 'Grid.Row',
properties: {
[uid()]: {
type: 'void',
'x-component': 'Grid.Col',
properties: {
[schema.name || uid()]: schema,
},
},
},
};
};
export const removeTableColumn = (schema, cb) => {
cb(schema, {
removeParentsIfNoChildren: true,
breakRemoveOn: {
'x-component': 'ArrayTable',
},
});
};
export const removeGridFormItem = (schema, cb) => {
cb(schema, {
removeParentsIfNoChildren: true,
breakRemoveOn: {
'x-component': 'Grid',
},
});
};
export const useTableColumnInitializerFields = () => {
const { name, fields } = useCollection();
return (
fields
// .filter((field) => field?.uiSchema?.title)
.map((field) => {
return {
type: 'item',
title: field?.uiSchema?.title || field.name,
schema: {
name: field.name,
'x-collection-field': `${name}.${field.name}`,
'x-component': 'CollectionField',
},
component: 'CollectionFieldInitializer',
remove: removeTableColumn,
} as SchemaInitializerItemOptions;
})
);
};
export const useFormItemInitializerFields = () => {
const { name, fields } = useCollection();
return fields?.map((field) => {
return {
type: 'item',
title: field?.uiSchema?.title || field.name,
component: 'CollectionFieldInitializer',
remove: removeGridFormItem,
schema: {
name: field.name,
'x-designer': 'FormItem.Designer',
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-collection-field': `${name}.${field.name}`,
},
} as SchemaInitializerItemOptions;
});
};
const findSchema = (schema: Schema, key: string, action: string) => {
return schema.reduceProperties((buf, s) => {
if (s[key] === action) {
return s;
}
const c = findSchema(s, key, action);
if (c) {
return c;
}
return buf;
});
};
const removeSchema = (schema, cb) => {
return cb(schema);
};
export const useCurrentSchema = (action: string, key: string, find = findSchema, rm = removeSchema) => {
const fieldSchema = useFieldSchema();
const { remove } = useDesignable();
const schema = find(fieldSchema, key, action);
return {
schema,
exists: !!schema,
remove() {
schema && rm(schema, remove);
},
};
};

View File

@ -2,6 +2,7 @@ import { ISchema, observer } from '@formily/react';
import { Button, Dropdown, Menu } from 'antd'; import { Button, Dropdown, Menu } from 'antd';
import React, { createContext, useContext, useState } from 'react'; import React, { createContext, useContext, useState } from 'react';
import { useDesignable } from '../schema-component/hooks'; import { useDesignable } from '../schema-component/hooks';
import { initializes, items } from './Initializers';
import { import {
SchemaInitializerButtonProps, SchemaInitializerButtonProps,
SchemaInitializerItemComponent, SchemaInitializerItemComponent,
@ -15,8 +16,21 @@ export const SchemaInitializerItemContext = createContext(null);
export const SchemaInitializer = () => null; export const SchemaInitializer = () => null;
SchemaInitializer.items = items;
SchemaInitializer.initializes = initializes;
SchemaInitializer.Button = observer((props: SchemaInitializerButtonProps) => { SchemaInitializer.Button = observer((props: SchemaInitializerButtonProps) => {
const { insert, wrap = defaultWrap, items = [], insertPosition, dropdown, style, ...others } = props; const {
title,
insert,
wrap = defaultWrap,
items = [],
insertPosition = 'beforeEnd',
dropdown,
style,
...others
} = props;
let { insertAdjacent, findComponent, designable } = useDesignable(); let { insertAdjacent, findComponent, designable } = useDesignable();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const insertSchema = (schema) => { const insertSchema = (schema) => {
@ -102,7 +116,9 @@ SchemaInitializer.Button = observer((props: SchemaInitializerButtonProps) => {
borderColor: '#f18b62', borderColor: '#f18b62',
color: '#f18b62', color: '#f18b62',
}} }}
/> >
{props.children || props.title}
</Button>
</Dropdown> </Dropdown>
); );
}); });

View File

@ -1,7 +1,54 @@
import React from 'react'; import { isPlainObj } from '@formily/shared';
import React, { createContext, useContext } from 'react';
import { SchemaComponentOptions } from '../schema-component'; import { SchemaComponentOptions } from '../schema-component';
import * as initializers from './Initializers'; import { initializes as globals, items } from './Initializers';
import { SchemaInitializer } from './SchemaInitializer';
export const SchemaInitializerProvider: React.FC = (props) => { const SchemaInitializerContext = createContext(null);
return <SchemaComponentOptions components={initializers}>{props.children}</SchemaComponentOptions>;
export interface SchemaInitializerProviderProps {
initializers?: Record<string, any>;
}
export const useSchemaInitializer = (name: string) => {
const initializers = useContext(SchemaInitializerContext);
const render = (component?: any, props?: any) => {
return component && React.createElement(component, props);
};
if (!name) {
return { exists: false, render };
}
const initializer = initializers?.[name];
if (!initializer) {
return { exists: false, render };
}
if (isPlainObj(initializer)) {
return {
exists: true,
render: (props?: any) => {
const component = (initializer as any).component || SchemaInitializer.Button;
return render(component, { ...initializer, ...props });
},
};
}
return {
exists: true,
render: (props?: any) => render(initializer, props),
};
};
export const SchemaInitializerProvider: React.FC<SchemaInitializerProviderProps> = (props) => {
const { initializers, children } = props;
return (
<SchemaInitializerContext.Provider value={{ ...globals, ...initializers }}>
<SchemaComponentOptions components={{ ...items }}>{children}</SchemaComponentOptions>
</SchemaInitializerContext.Provider>
);
}; };

View File

@ -1,7 +1,13 @@
import { FormOutlined, TableOutlined } from '@ant-design/icons'; import { TableOutlined } from '@ant-design/icons';
import { Field } from '@formily/core'; import { Field } from '@formily/core';
import { observer, useField } from '@formily/react'; import { observer, useField } from '@formily/react';
import { SchemaComponent, SchemaComponentProvider, SchemaInitializer } from '@nocobase/client'; import {
SchemaComponent,
SchemaComponentProvider,
SchemaInitializer,
SchemaInitializerProvider,
useSchemaInitializer
} from '@nocobase/client';
import React from 'react'; import React from 'react';
const Hello = observer((props) => { const Hello = observer((props) => {
@ -13,36 +19,7 @@ const Hello = observer((props) => {
); );
}); });
const AddBlockButton = observer((props: any) => { const TableBlockInitializer = SchemaInitializer.itemWrap((props) => {
return (
<SchemaInitializer.Button
wrap={(schema) => schema}
insertPosition={'beforeBegin'}
items={[
{
type: 'itemGroup',
title: 'Data blocks',
children: [
{
type: 'item',
title: 'Table',
component: TableBlockInitializerItem,
},
{
type: 'item',
title: 'Form',
component: FormBlockInitializerItem,
},
],
},
]}
>
Add block
</SchemaInitializer.Button>
);
});
const TableBlockInitializerItem = SchemaInitializer.itemWrap((props) => {
const { insert } = props; const { insert } = props;
const items: any = [ const items: any = [
{ {
@ -77,48 +54,69 @@ const TableBlockInitializerItem = SchemaInitializer.itemWrap((props) => {
); );
}); });
const FormBlockInitializerItem = SchemaInitializer.itemWrap((props) => { const initializers = {
const { insert } = props; AddBlock: {
return ( title: 'Add block',
<SchemaInitializer.Item insertPosition: 'beforeBegin',
icon={<FormOutlined />} items: [
onClick={() => { {
insert({ type: 'itemGroup',
type: 'void', title: 'Data blocks',
title: 'Form', children: [
'x-component': 'Hello', {
}); type: 'item',
}} title: 'Table',
/> component: 'TableBlockInitializer',
); },
{
type: 'item',
title: 'Form',
component: 'GeneralInitializer',
schema: {
type: 'void',
title: 'Form',
'x-component': 'Hello',
},
},
],
},
],
},
};
const AddBlockButton = observer((props: any) => {
const { render } = useSchemaInitializer('AddBlock');
return <>{render()}</>;
}); });
export default function App() { export default function App() {
return ( return (
<SchemaComponentProvider components={{ Hello, AddBlockButton }}> <SchemaComponentProvider components={{ TableBlockInitializer, Hello, AddBlockButton }}>
<SchemaComponent <SchemaInitializerProvider initializers={initializers}>
schema={{ <SchemaComponent
type: 'void', schema={{
name: 'page', type: 'void',
'x-component': 'div', name: 'page',
properties: { 'x-component': 'div',
hello1: { properties: {
type: 'void', hello1: {
title: 'Test1', type: 'void',
'x-component': 'Hello', title: 'Test1',
'x-component': 'Hello',
},
hello2: {
type: 'void',
title: 'Test2',
'x-component': 'Hello',
},
initializer: {
type: 'void',
'x-component': 'AddBlockButton',
},
}, },
hello2: { }}
type: 'void', />
title: 'Test2', </SchemaInitializerProvider>
'x-component': 'Hello',
},
initializer: {
type: 'void',
'x-component': 'AddBlockButton',
},
},
}}
/>
</SchemaComponentProvider> </SchemaComponentProvider>
); );
} }

View File

@ -1,117 +1,77 @@
import { observer, useFieldSchema } from '@formily/react';
import { import {
Action, Action,
ActionBar, ActionBar,
SchemaComponent, SchemaComponent,
SchemaComponentProvider, SchemaComponentProvider,
SchemaInitializer, SchemaInitializerProvider
useDesignable
} from '@nocobase/client'; } from '@nocobase/client';
import { Switch } from 'antd';
import React from 'react'; import React from 'react';
const AddActionButton = observer((props: any) => { const initializers = {
return ( AddAction: {
<SchemaInitializer.Button title: 'Configure actions',
insertPosition={'beforeEnd'} insertPosition: 'beforeEnd',
style={{ marginLeft: 8 }} style: { marginLeft: 8 },
items={[ items: [
{ {
type: 'itemGroup', type: 'itemGroup',
title: 'Enable actions', title: 'Enable actions',
children: [ children: [
{ {
type: 'item', type: 'item',
title: 'Create',
component: 'ActionInitializer',
schema: {
title: 'Create', title: 'Create',
component: InitializeAction, 'x-action': 'posts:create',
schema: { 'x-component': 'Action',
title: 'Create', 'x-designer': 'Action.Designer',
'x-action': 'posts:create', 'x-align': 'left',
'x-align': 'left',
},
}, },
{ },
type: 'item', {
type: 'item',
title: 'Update',
component: 'ActionInitializer',
schema: {
title: 'Update', title: 'Update',
component: InitializeAction, 'x-action': 'posts:update',
schema: { 'x-component': 'Action',
title: 'Update', 'x-designer': 'Action.Designer',
'x-action': 'posts:update', 'x-align': 'right',
},
}, },
], },
}, ],
]} },
> ],
Configure actions },
</SchemaInitializer.Button>
);
});
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 default function App() { export default function App() {
return ( return (
<SchemaComponentProvider components={{ ActionBar, Action, AddActionButton }}> <SchemaComponentProvider components={{ ActionBar, Action }}>
<SchemaComponent <SchemaInitializerProvider initializers={initializers}>
schema={{ <SchemaComponent
type: 'void', schema={{
name: 'page', type: 'void',
'x-component': 'ActionBar', name: 'page',
// 指定初始化的按钮组件, 'x-component': 'ActionBar',
// Table、Form、Details、Calendar、Kanban 等等不同区块 // 指定初始化的按钮组件,
// 可以根据情况组装自己的 initializer // Table、Form、Details、Calendar、Kanban 等等不同区块
'x-action-initializer': 'AddActionButton', // 可以根据情况组装自己的 initializer
properties: { 'x-initializer': 'AddAction',
action1: { properties: {
type: 'void', action1: {
title: 'Update', type: 'void',
// 使用 x-action 来标记 action schema title: 'Update',
'x-action': 'posts:update', // 使用 x-action 来标记 action schema
'x-component': 'Action', 'x-action': 'posts:update',
'x-component': 'Action',
},
}, },
}, }}
}} />
/> </SchemaInitializerProvider>
</SchemaComponentProvider> </SchemaComponentProvider>
); );
} }

View File

@ -1,4 +1,3 @@
import { observer, Schema, useFieldSchema } from '@formily/react';
import { import {
Form, Form,
FormItem, FormItem,
@ -6,18 +5,18 @@ import {
SchemaComponent, SchemaComponent,
SchemaComponentProvider, SchemaComponentProvider,
SchemaInitializer, SchemaInitializer,
SchemaInitializerItemOptions, SchemaInitializerItemOptions
useDesignable
} from '@nocobase/client'; } from '@nocobase/client';
import { Input, Switch } from 'antd'; import { Input } from 'antd';
import React from 'react'; import React from 'react';
import { SchemaInitializerProvider, useSchemaInitializer } from '..';
const useFormItemInitializerFields = () => { const useFormItemInitializerFields = () => {
return [ return [
{ {
type: 'item', type: 'item',
title: 'Name', title: 'Name',
component: InitializeFormItem, component: 'CollectionFieldInitializer',
schema: { schema: {
type: 'string', type: 'string',
title: 'Name', title: 'Name',
@ -30,7 +29,7 @@ const useFormItemInitializerFields = () => {
{ {
type: 'item', type: 'item',
title: 'Title', title: 'Title',
component: InitializeFormItem, component: 'CollectionFieldInitializer',
schema: { schema: {
type: 'string', type: 'string',
title: 'Title', title: 'Title',
@ -43,79 +42,7 @@ const useFormItemInitializerFields = () => {
] as SchemaInitializerItemOptions[]; ] as SchemaInitializerItemOptions[];
}; };
const AddFormItemButton = observer((props: any) => { const TextInitializer = SchemaInitializer.itemWrap((props) => {
return (
<SchemaInitializer.Button
wrap={(schema) => schema}
insertPosition={'beforeEnd'}
items={[
{
type: 'itemGroup',
title: 'Display fields',
children: useFormItemInitializerFields(),
},
{
type: 'divider',
},
{
type: 'item',
title: 'Add text',
component: InitializeTextFormItem,
},
]}
>
Configure fields
</SchemaInitializer.Button>
);
});
const useCurrentFieldSchema = (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);
},
};
};
const InitializeFormItem = SchemaInitializer.itemWrap((props) => {
const { item, insert } = props;
const { exists, remove } = useCurrentFieldSchema(item.schema['x-collection-field']);
return (
<SchemaInitializer.Item
onClick={() => {
if (exists) {
return remove();
}
insert({
...item.schema,
});
}}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
{item.title} <Switch size={'small'} checked={exists} />
</div>
</SchemaInitializer.Item>
);
});
const InitializeTextFormItem = SchemaInitializer.itemWrap((props) => {
const { insert } = props; const { insert } = props;
return ( return (
<SchemaInitializer.Item <SchemaInitializer.Item
@ -132,41 +59,66 @@ const InitializeTextFormItem = SchemaInitializer.itemWrap((props) => {
}); });
const Page = (props) => { const Page = (props) => {
const { render } = useSchemaInitializer('AddFormItem');
return ( return (
<div> <div>
{props.children} {props.children}
<AddFormItemButton /> {render()}
</div> </div>
); );
}; };
export default function App() { export default function App() {
const initializers = {
AddFormItem: {
title: 'Configure fields',
insertPosition: 'beforeEnd',
items: [
{
type: 'itemGroup',
title: 'Display fields',
// 从 hook 动态加载字段
children: useFormItemInitializerFields(),
},
{
type: 'divider',
},
{
type: 'item',
title: 'Add text',
component: 'TextInitializer',
},
],
},
};
return ( return (
<SchemaComponentProvider components={{ Page, Form, Input, FormItem, Markdown }}> <SchemaComponentProvider components={{ TextInitializer, Page, Form, Input, FormItem, Markdown }}>
<SchemaComponent <SchemaInitializerProvider initializers={initializers}>
schema={{ <SchemaComponent
type: 'void', schema={{
name: 'page', type: 'void',
'x-decorator': 'Form', name: 'page',
'x-component': 'Page', 'x-decorator': 'Form',
properties: { 'x-component': 'Page',
title: { properties: {
type: 'string', title: {
title: 'Title', type: 'string',
'x-component': 'Input', title: 'Title',
'x-decorator': 'FormItem', 'x-component': 'Input',
'x-collection-field': 'posts.title', 'x-decorator': 'FormItem',
'x-collection-field': 'posts.title',
},
name: {
type: 'string',
title: 'Name',
'x-component': 'Input',
'x-decorator': 'FormItem',
'x-collection-field': 'posts.name',
},
}, },
name: { }}
type: 'string', />
title: 'Name', </SchemaInitializerProvider>
'x-component': 'Input',
'x-decorator': 'FormItem',
'x-collection-field': 'posts.name',
},
},
}}
/>
</SchemaComponentProvider> </SchemaComponentProvider>
); );
} }

View File

@ -1,116 +1,90 @@
import { observer, Schema, useFieldSchema } from '@formily/react'; import { uid } from '@formily/shared';
import { import {
ArrayTable, ArrayTable,
Input, Input,
SchemaComponent, SchemaComponent,
SchemaComponentProvider, SchemaComponentProvider,
SchemaInitializer,
SchemaInitializerItemOptions, SchemaInitializerItemOptions,
useDesignable SchemaInitializerProvider
} from '@nocobase/client'; } from '@nocobase/client';
import { Switch } from 'antd';
import React from 'react'; import React from 'react';
const removeColumn = (schema, cb) => {
cb(schema, {
removeParentsIfNoChildren: true,
breakRemoveOn: {
'x-component': 'ArrayTable',
},
});
};
const useTableColumnInitializerFields = () => { const useTableColumnInitializerFields = () => {
const fields: SchemaInitializerItemOptions[] = [ const fields: SchemaInitializerItemOptions[] = [
{ {
type: 'item', type: 'item',
title: 'Name', title: 'Name',
remove: removeColumn,
schema: { schema: {
type: 'string',
name: 'name', name: 'name',
type: 'string',
title: 'Name',
'x-collection-field': 'posts.name', 'x-collection-field': 'posts.name',
'x-component': 'Input', 'x-component': 'Input',
'x-read-pretty': true,
}, },
component: ColumnInitializerItem, component: 'CollectionFieldInitializer',
}, },
{ {
type: 'item', type: 'item',
title: 'Title', title: 'Title',
remove: removeColumn,
schema: { schema: {
type: 'string',
name: 'title', name: 'title',
type: 'string',
title: 'Title',
'x-collection-field': 'posts.title', 'x-collection-field': 'posts.title',
'x-component': 'Input', 'x-component': 'Input',
'x-read-pretty': true,
}, },
component: ColumnInitializerItem, component: 'CollectionFieldInitializer',
}, },
]; ];
return fields; return fields;
}; };
export const AddTableColumn = observer((props: any) => { const columnWrap = (s) => {
return ( return {
<SchemaInitializer.Button name: [uid()],
wrap={(schema) => schema} type: 'void',
insertPosition={'beforeEnd'} title: s.title,
items={[ 'x-component': 'ArrayTable.Column',
properties: {
[s.name]: {
...s,
},
},
};
};
// 因为有个动态获取字段的 hook所以这里将 SchemaInitializerProvider 自定义的处理了一下
const CustomSchemaInitializerProvider: React.FC = (props) => {
const initializers = {
AddColumn: {
insertPosition: 'beforeEnd',
title: 'Configure columns',
wrap: columnWrap,
items: [
{ {
type: 'itemGroup', type: 'itemGroup',
title: 'Display fields', title: 'Display fields',
children: useTableColumnInitializerFields(), children: useTableColumnInitializerFields(),
}, },
]} ],
>
Configure columns
</SchemaInitializer.Button>
);
});
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);
}, },
}; };
return <SchemaInitializerProvider initializers={initializers}>{props.children}</SchemaInitializerProvider>;
}; };
export 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',
title: item.schema.name,
'x-component': 'ArrayTable.Column',
properties: {
[item.schema.name]: {
'x-read-pretty': true,
...item.schema,
},
},
});
}}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
{item.title} <Switch size={'small'} checked={exists} />
</div>
</SchemaInitializer.Item>
);
});
const schema = { const schema = {
type: 'object', type: 'object',
properties: { properties: {
@ -122,7 +96,7 @@ const schema = {
{ id: 3, name: 'Name3' }, { id: 3, name: 'Name3' },
], ],
'x-component': 'ArrayTable', 'x-component': 'ArrayTable',
'x-column-initializer': 'AddTableColumn', 'x-initializer': 'AddColumn',
'x-component-props': { 'x-component-props': {
rowKey: 'id', rowKey: 'id',
}, },
@ -147,8 +121,10 @@ const schema = {
export default function App() { export default function App() {
return ( return (
<SchemaComponentProvider components={{ AddTableColumn, Input, ArrayTable }}> <SchemaComponentProvider components={{ Input, ArrayTable }}>
<SchemaComponent schema={schema} /> <CustomSchemaInitializerProvider>
<SchemaComponent schema={schema} />
</CustomSchemaInitializerProvider>
</SchemaComponentProvider> </SchemaComponentProvider>
); );
} }

View File

@ -150,3 +150,70 @@ const TableBlockInitializerItem = SchemaInitializer.itemWrap((props) => {
### Table.Column ### Table.Column
<code src="./demos/demo4.tsx" /> <code src="./demos/demo4.tsx" />
## 配置
核心的参数
```tsx | pure
const initializers = {
xxx: {
title: '{{t("Add block")}}',
insertPosition: 'beforeEnd',
items: [], // 在这里配置
},
};
<SchemaInitializerProvider initializers={initializers}>
{/* children */}
</SchemaInitializerProvider>
```
items 例子:
- Initializer.Item 只有添加的逻辑
- Initializer.SwitchItem 可以添加或移除
```ts
{
BlockInitializer: {
insertPosition: 'beforeEnd',
items: [
{
type: 'itemGroup',
title: "{{t('Enable actions')}}",
children: [
{
type: 'item',
title: "{{t('Tody')}}",
component: 'Initializer.SwitchItem',
schema: {
title: "{{t('Tody')}}",
'x-component': 'Calendar.Today',
'x-action': `calendar:today`,
'x-align': 'left',
},
},
{
type: 'item',
title: "{{t('Tody')}}",
component: 'Initializer.Item',
schema: {
title: "{{t('Tody')}}",
'x-component': 'Calendar.Today',
'x-action': `calendar:today`,
'x-align': 'left',
},
},
],
},
],
},
}
```
内置的 Initializer 有:
- `GeneralInitializer` 通用的配置项,只有插入的逻辑
- `ActionInitializer` 用于配置操作按钮,有插入和移除的逻辑
- `CollectionFieldInitializer` 用于配置数据表字段,有插入和移除的逻辑

View File

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

View File

@ -1,4 +1,4 @@
import { ISchema } from '@formily/react'; import { ISchema, Schema } from '@formily/react';
import { ButtonProps, DropDownProps, MenuItemProps } from 'antd'; import { ButtonProps, DropDownProps, MenuItemProps } from 'antd';
export interface SchemaInitializerButtonProps extends ButtonProps { export interface SchemaInitializerButtonProps extends ButtonProps {
@ -25,10 +25,21 @@ interface SubMenuOptions extends ItemCommonOptions {
children?: SchemaInitializerItemOptions[]; children?: SchemaInitializerItemOptions[];
} }
type BreakFn = (s: ISchema) => boolean;
interface RemoveOptions {
removeParentsIfNoChildren?: boolean;
breakRemoveOn?: ISchema | BreakFn;
}
type RemoveCallback = (s: Schema, options?: RemoveOptions) => void;
interface ItemOptions extends ItemCommonOptions { interface ItemOptions extends ItemCommonOptions {
type: 'item'; type: 'item';
component?: any; component?: any;
schema?: ISchema; schema?: ISchema;
remove?: (schema: Schema, cb: RemoveCallback) => void;
find?: (schema: Schema, current: string) => Schema | null | undefined;
[key: string]: any; [key: string]: any;
} }

View File

@ -21,7 +21,7 @@ export class UiRoutesStoragePlugin extends Plugin {
type: 'void', type: 'void',
'x-component': 'Menu', 'x-component': 'Menu',
'x-designer': 'Menu.Designer', 'x-designer': 'Menu.Designer',
'x-initializer': 'MenuItemInitializer', 'x-initializer': 'MenuItemInitializers',
'x-component-props': { 'x-component-props': {
mode: 'mix', mode: 'mix',
theme: 'dark', theme: 'dark',