feat: block templates (#240)
* feat: block templates * feat: improve code * feat: improve code * feat: schema settings for block templates * feat: improve code * feat: transition * feat: transition
This commit is contained in:
parent
5c92d3ba46
commit
2fb019c392
@ -5,6 +5,8 @@ import {
|
|||||||
AntdSchemaComponentProvider,
|
AntdSchemaComponentProvider,
|
||||||
APIClientProvider,
|
APIClientProvider,
|
||||||
AuthLayout,
|
AuthLayout,
|
||||||
|
BlockTemplateDetails,
|
||||||
|
BlockTemplatePage,
|
||||||
ChinaRegionProvider,
|
ChinaRegionProvider,
|
||||||
CollectionManagerShortcut,
|
CollectionManagerShortcut,
|
||||||
compose,
|
compose,
|
||||||
@ -13,19 +15,20 @@ import {
|
|||||||
i18n,
|
i18n,
|
||||||
MenuItemInitializers,
|
MenuItemInitializers,
|
||||||
PluginManagerProvider,
|
PluginManagerProvider,
|
||||||
|
RemoteRouteSwitchProvider,
|
||||||
// RemoteCollectionManagerProvider,
|
// RemoteCollectionManagerProvider,
|
||||||
RouteSchemaComponent,
|
RouteSchemaComponent,
|
||||||
RouteSwitch,
|
RouteSwitch,
|
||||||
RouteSwitchProvider,
|
|
||||||
SchemaComponentProvider,
|
SchemaComponentProvider,
|
||||||
SchemaInitializerProvider,
|
SchemaInitializerProvider,
|
||||||
|
SchemaTemplateShortcut,
|
||||||
SigninPage,
|
SigninPage,
|
||||||
SignupPage,
|
SignupPage,
|
||||||
SystemSettingsProvider,
|
SystemSettingsProvider,
|
||||||
SystemSettingsShortcut,
|
SystemSettingsShortcut,
|
||||||
useRequest
|
useRoutes
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { notification, Spin } from 'antd';
|
import { notification } from 'antd';
|
||||||
import 'antd/dist/antd.css';
|
import 'antd/dist/antd.css';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { I18nextProvider } from 'react-i18next';
|
import { I18nextProvider } from 'react-i18next';
|
||||||
@ -50,6 +53,20 @@ const providers = [
|
|||||||
[APIClientProvider, { apiClient }],
|
[APIClientProvider, { apiClient }],
|
||||||
[I18nextProvider, { i18n }],
|
[I18nextProvider, { i18n }],
|
||||||
[AntdConfigProvider, { remoteLocale: true }],
|
[AntdConfigProvider, { remoteLocale: true }],
|
||||||
|
[
|
||||||
|
RemoteRouteSwitchProvider,
|
||||||
|
{
|
||||||
|
components: {
|
||||||
|
AuthLayout,
|
||||||
|
AdminLayout,
|
||||||
|
RouteSchemaComponent,
|
||||||
|
SigninPage,
|
||||||
|
SignupPage,
|
||||||
|
BlockTemplatePage,
|
||||||
|
BlockTemplateDetails,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
SystemSettingsProvider,
|
SystemSettingsProvider,
|
||||||
[
|
[
|
||||||
PluginManagerProvider,
|
PluginManagerProvider,
|
||||||
@ -59,39 +76,30 @@ const providers = [
|
|||||||
DesignableSwitch,
|
DesignableSwitch,
|
||||||
CollectionManagerShortcut,
|
CollectionManagerShortcut,
|
||||||
SystemSettingsShortcut,
|
SystemSettingsShortcut,
|
||||||
|
SchemaTemplateShortcut,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[SchemaComponentProvider, { components: { Link, NavLink } }],
|
[SchemaComponentProvider, { components: { Link, NavLink } }],
|
||||||
// RemoteCollectionManagerProvider,
|
// RemoteCollectionManagerProvider,
|
||||||
[SchemaInitializerProvider, { initializers: { MenuItemInitializers } }],
|
|
||||||
AntdSchemaComponentProvider,
|
|
||||||
ChinaRegionProvider,
|
|
||||||
[DocumentTitleProvider, { addonAfter: 'NocoBase' }],
|
|
||||||
[
|
[
|
||||||
RouteSwitchProvider,
|
SchemaInitializerProvider,
|
||||||
{
|
{
|
||||||
components: {
|
initializers: {
|
||||||
AuthLayout,
|
MenuItemInitializers,
|
||||||
AdminLayout,
|
|
||||||
RouteSchemaComponent,
|
|
||||||
SigninPage,
|
|
||||||
SignupPage,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
AntdSchemaComponentProvider,
|
||||||
|
ChinaRegionProvider,
|
||||||
|
[DocumentTitleProvider, { addonAfter: 'NocoBase' }],
|
||||||
];
|
];
|
||||||
|
|
||||||
const App = compose(...providers)(() => {
|
const App = compose(...providers)(() => {
|
||||||
const { data, loading } = useRequest({
|
const routes = useRoutes();
|
||||||
url: 'uiRoutes:getAccessible',
|
|
||||||
});
|
|
||||||
if (loading) {
|
|
||||||
return <Spin />;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<RouteSwitch routes={data?.data || []} />
|
<RouteSwitch routes={routes} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -19,6 +19,7 @@ export * from './route-switch';
|
|||||||
export * from './schema-component';
|
export * from './schema-component';
|
||||||
export * from './schema-initializer';
|
export * from './schema-initializer';
|
||||||
export * from './schema-settings';
|
export * from './schema-settings';
|
||||||
|
export * from './schema-templates';
|
||||||
export * from './settings-form';
|
export * from './settings-form';
|
||||||
export * from './system-settings';
|
export * from './system-settings';
|
||||||
export * from './user';
|
export * from './user';
|
||||||
|
@ -279,5 +279,10 @@
|
|||||||
|
|
||||||
"Add card": "添加卡片",
|
"Add card": "添加卡片",
|
||||||
"edit title": "修改标题",
|
"edit title": "修改标题",
|
||||||
"turn page": "翻页"
|
"turn page": "翻页",
|
||||||
|
"Save as template": "保存为模板",
|
||||||
|
"Block templates": "区块模板",
|
||||||
|
"Convert reference to duplicate": "模板引用转为复制",
|
||||||
|
"Template name": "模板名称",
|
||||||
|
"Block type": "区块类型"
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,20 @@
|
|||||||
|
import { Spin } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useRequest } from '../api-client';
|
||||||
import { RouteSwitchContext } from './context';
|
import { RouteSwitchContext } from './context';
|
||||||
import { RouteSwitchProviderProps } from './types';
|
import { RouteSwitchProviderProps } from './types';
|
||||||
|
|
||||||
export function RouteSwitchProvider(props: RouteSwitchProviderProps) {
|
export function RouteSwitchProvider(props: RouteSwitchProviderProps) {
|
||||||
const { children, components } = props;
|
const { children, components, routes } = props;
|
||||||
return <RouteSwitchContext.Provider value={{ components }}>{children}</RouteSwitchContext.Provider>;
|
return <RouteSwitchContext.Provider value={{ routes, components }}>{children}</RouteSwitchContext.Provider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RemoteRouteSwitchProvider(props: RouteSwitchProviderProps) {
|
||||||
|
const { data, loading } = useRequest({
|
||||||
|
url: 'uiRoutes:getAccessible',
|
||||||
|
});
|
||||||
|
if (loading) {
|
||||||
|
return <Spin />;
|
||||||
|
}
|
||||||
|
return <RouteSwitchProvider {...props} routes={data?.data || []} />;
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
PluginManager,
|
PluginManager,
|
||||||
RemoteCollectionManagerProvider,
|
RemoteCollectionManagerProvider,
|
||||||
RemoteSchemaComponent,
|
RemoteSchemaComponent,
|
||||||
|
RemoteSchemaTemplateManagerProvider,
|
||||||
useDocumentTitle,
|
useDocumentTitle,
|
||||||
useRoute,
|
useRoute,
|
||||||
useSystemSettings
|
useSystemSettings
|
||||||
@ -94,6 +95,7 @@ const InternalAdminLayout = (props: any) => {
|
|||||||
{ component: 'DesignableSwitch', pin: true },
|
{ component: 'DesignableSwitch', pin: true },
|
||||||
{ component: 'CollectionManagerShortcut', pin: true },
|
{ component: 'CollectionManagerShortcut', pin: true },
|
||||||
{ component: 'ACLShortcut', pin: true },
|
{ component: 'ACLShortcut', pin: true },
|
||||||
|
{ component: 'SchemaTemplateShortcut', pin: true },
|
||||||
{ component: 'SystemSettingsShortcut' },
|
{ component: 'SystemSettingsShortcut' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
@ -110,11 +112,13 @@ const InternalAdminLayout = (props: any) => {
|
|||||||
|
|
||||||
export const AdminLayout = (props) => {
|
export const AdminLayout = (props) => {
|
||||||
return (
|
return (
|
||||||
|
<RemoteSchemaTemplateManagerProvider>
|
||||||
<RemoteCollectionManagerProvider>
|
<RemoteCollectionManagerProvider>
|
||||||
<CurrentUserProvider>
|
<CurrentUserProvider>
|
||||||
<InternalAdminLayout {...props} />
|
<InternalAdminLayout {...props} />
|
||||||
</CurrentUserProvider>
|
</CurrentUserProvider>
|
||||||
</RemoteCollectionManagerProvider>
|
</RemoteCollectionManagerProvider>
|
||||||
|
</RemoteSchemaTemplateManagerProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import { RouteProps } from './types';
|
|||||||
|
|
||||||
export const RouteSwitchContext = createContext({
|
export const RouteSwitchContext = createContext({
|
||||||
components: {},
|
components: {},
|
||||||
|
routes: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
export const RouteContext = createContext<RouteProps>(null);
|
export const RouteContext = createContext<RouteProps>(null);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useContext } from 'react';
|
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
|
import { useContext } from 'react';
|
||||||
import { RouteContext, RouteSwitchContext } from './context';
|
import { RouteContext, RouteSwitchContext } from './context';
|
||||||
|
|
||||||
export function useRouteComponent(name?: string) {
|
export function useRouteComponent(name?: string) {
|
||||||
@ -13,3 +13,8 @@ export function useRouteComponent(name?: string) {
|
|||||||
export function useRoute() {
|
export function useRoute() {
|
||||||
return useContext(RouteContext);
|
return useContext(RouteContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useRoutes() {
|
||||||
|
const { routes } = useContext(RouteSwitchContext);
|
||||||
|
return routes || [];
|
||||||
|
}
|
||||||
|
@ -23,6 +23,7 @@ export interface RouteProps {
|
|||||||
export interface RouteSwitchProviderProps {
|
export interface RouteSwitchProviderProps {
|
||||||
components?: any;
|
components?: any;
|
||||||
children?: any;
|
children?: any;
|
||||||
|
routes?: RouteRedirectProps[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RouteRedirectProps = RedirectProps | RouteProps;
|
export type RouteRedirectProps = RedirectProps | RouteProps;
|
||||||
|
@ -4,6 +4,7 @@ import { useCompile, useDesignable } from '../..';
|
|||||||
import { useCollection, useResourceActionContext } from '../../../collection-manager';
|
import { useCollection, useResourceActionContext } from '../../../collection-manager';
|
||||||
import { useCollectionFilterOptions } from '../../../collection-manager/action-hooks';
|
import { useCollectionFilterOptions } from '../../../collection-manager/action-hooks';
|
||||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||||
|
import { useSchemaTemplate } from '../../../schema-templates';
|
||||||
|
|
||||||
export const CalendarDesigner = () => {
|
export const CalendarDesigner = () => {
|
||||||
const field = useField();
|
const field = useField();
|
||||||
@ -13,6 +14,7 @@ export const CalendarDesigner = () => {
|
|||||||
const ctx = useResourceActionContext();
|
const ctx = useResourceActionContext();
|
||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
|
const template = useSchemaTemplate();
|
||||||
const defaultFilter = fieldSchema?.['x-decorator-props']?.request?.params?.filter || {};
|
const defaultFilter = fieldSchema?.['x-decorator-props']?.request?.params?.filter || {};
|
||||||
const options = fields?.map((field) => {
|
const options = fields?.map((field) => {
|
||||||
return {
|
return {
|
||||||
@ -23,7 +25,7 @@ export const CalendarDesigner = () => {
|
|||||||
const calendarSchema = fieldSchema.properties.calendar;
|
const calendarSchema = fieldSchema.properties.calendar;
|
||||||
const fieldNames = calendarSchema?.['x-component-props']?.['fieldNames'] || {};
|
const fieldNames = calendarSchema?.['x-component-props']?.['fieldNames'] || {};
|
||||||
return (
|
return (
|
||||||
<GeneralSchemaDesigner title={title || name}>
|
<GeneralSchemaDesigner template={template} title={title || name}>
|
||||||
<SchemaSettings.SelectItem
|
<SchemaSettings.SelectItem
|
||||||
title={'标题字段'}
|
title={'标题字段'}
|
||||||
value={fieldNames.title}
|
value={fieldNames.title}
|
||||||
@ -128,6 +130,8 @@ export const CalendarDesigner = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<SchemaSettings.Divider />
|
<SchemaSettings.Divider />
|
||||||
|
<SchemaSettings.Template componentName={'Calendar'} collectionName={name} />
|
||||||
|
<SchemaSettings.Divider />
|
||||||
<SchemaSettings.Remove
|
<SchemaSettings.Remove
|
||||||
removeParentsIfNoChildren
|
removeParentsIfNoChildren
|
||||||
breakRemoveOn={{
|
breakRemoveOn={{
|
||||||
|
@ -6,6 +6,8 @@ export const FormDesigner = () => {
|
|||||||
const { name, title } = useCollection();
|
const { name, title } = useCollection();
|
||||||
return (
|
return (
|
||||||
<GeneralSchemaDesigner title={title || name}>
|
<GeneralSchemaDesigner title={title || name}>
|
||||||
|
<SchemaSettings.Template componentName={'Form'} collectionName={name} />
|
||||||
|
<SchemaSettings.Divider />
|
||||||
<SchemaSettings.Remove
|
<SchemaSettings.Remove
|
||||||
removeParentsIfNoChildren
|
removeParentsIfNoChildren
|
||||||
breakRemoveOn={{
|
breakRemoveOn={{
|
||||||
|
@ -8,6 +8,7 @@ import { useAttach, useComponent } from '../..';
|
|||||||
import { useRequest } from '../../../api-client';
|
import { useRequest } from '../../../api-client';
|
||||||
import { useCollection } from '../../../collection-manager';
|
import { useCollection } from '../../../collection-manager';
|
||||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||||
|
import { useSchemaTemplate } from '../../../schema-templates';
|
||||||
|
|
||||||
type Opts = Options<any, any> & { uid?: string };
|
type Opts = Options<any, any> & { uid?: string };
|
||||||
|
|
||||||
@ -113,8 +114,11 @@ export const Form: React.FC<FormProps> & { Designer?: any } = observer((props) =
|
|||||||
|
|
||||||
Form.Designer = () => {
|
Form.Designer = () => {
|
||||||
const { name, title } = useCollection();
|
const { name, title } = useCollection();
|
||||||
|
const template = useSchemaTemplate();
|
||||||
return (
|
return (
|
||||||
<GeneralSchemaDesigner title={title || name}>
|
<GeneralSchemaDesigner template={template} title={title || name}>
|
||||||
|
<SchemaSettings.Template componentName={'Form'} collectionName={name} />
|
||||||
|
<SchemaSettings.Divider />
|
||||||
<SchemaSettings.Remove
|
<SchemaSettings.Remove
|
||||||
removeParentsIfNoChildren
|
removeParentsIfNoChildren
|
||||||
breakRemoveOn={{
|
breakRemoveOn={{
|
||||||
|
@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useCollection, useResourceActionContext } from '../../../collection-manager';
|
import { useCollection, useResourceActionContext } from '../../../collection-manager';
|
||||||
import { useCollectionFilterOptions } from '../../../collection-manager/action-hooks';
|
import { useCollectionFilterOptions } from '../../../collection-manager/action-hooks';
|
||||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||||
|
import { useSchemaTemplate } from '../../../schema-templates';
|
||||||
import { useDesignable } from '../../hooks';
|
import { useDesignable } from '../../hooks';
|
||||||
|
|
||||||
export const KanbanDesigner = () => {
|
export const KanbanDesigner = () => {
|
||||||
@ -15,8 +16,9 @@ export const KanbanDesigner = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
const defaultFilter = fieldSchema?.['x-decorator-props']?.request?.params?.filter || {};
|
const defaultFilter = fieldSchema?.['x-decorator-props']?.request?.params?.filter || {};
|
||||||
|
const template = useSchemaTemplate();
|
||||||
return (
|
return (
|
||||||
<GeneralSchemaDesigner title={title || name}>
|
<GeneralSchemaDesigner template={template} title={title || name}>
|
||||||
<SchemaSettings.ModalItem
|
<SchemaSettings.ModalItem
|
||||||
title={'设置数据范围'}
|
title={'设置数据范围'}
|
||||||
schema={
|
schema={
|
||||||
@ -49,6 +51,8 @@ export const KanbanDesigner = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<SchemaSettings.Divider />
|
<SchemaSettings.Divider />
|
||||||
|
<SchemaSettings.Template componentName={'Kanban'} collectionName={name} />
|
||||||
|
<SchemaSettings.Divider />
|
||||||
<SchemaSettings.Remove
|
<SchemaSettings.Remove
|
||||||
removeParentsIfNoChildren
|
removeParentsIfNoChildren
|
||||||
breakRemoveOn={{
|
breakRemoveOn={{
|
||||||
|
@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useCollection, useResourceActionContext } from '../../../collection-manager';
|
import { useCollection, useResourceActionContext } from '../../../collection-manager';
|
||||||
import { useCollectionFilterOptions, useSortFields } from '../../../collection-manager/action-hooks';
|
import { useCollectionFilterOptions, useSortFields } from '../../../collection-manager/action-hooks';
|
||||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||||
|
import { useSchemaTemplate } from '../../../schema-templates';
|
||||||
import { useDesignable } from '../../hooks';
|
import { useDesignable } from '../../hooks';
|
||||||
|
|
||||||
export const TableVoidDesigner = () => {
|
export const TableVoidDesigner = () => {
|
||||||
@ -29,8 +30,9 @@ export const TableVoidDesigner = () => {
|
|||||||
direction: 'asc',
|
direction: 'asc',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
const template = useSchemaTemplate();
|
||||||
return (
|
return (
|
||||||
<GeneralSchemaDesigner title={title || name}>
|
<GeneralSchemaDesigner template={template} title={title || name}>
|
||||||
<SchemaSettings.SwitchItem
|
<SchemaSettings.SwitchItem
|
||||||
title={t('Enable drag and drop sorting')}
|
title={t('Enable drag and drop sorting')}
|
||||||
checked={field.decoratorProps.dragSort}
|
checked={field.decoratorProps.dragSort}
|
||||||
@ -193,6 +195,8 @@ export const TableVoidDesigner = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<SchemaSettings.Divider />
|
<SchemaSettings.Divider />
|
||||||
|
<SchemaSettings.Template componentName={'Table'} collectionName={name}/>
|
||||||
|
<SchemaSettings.Divider />
|
||||||
<SchemaSettings.Remove
|
<SchemaSettings.Remove
|
||||||
removeParentsIfNoChildren
|
removeParentsIfNoChildren
|
||||||
breakRemoveOn={{
|
breakRemoveOn={{
|
||||||
|
@ -11,6 +11,7 @@ interface CreateDesignableProps {
|
|||||||
current: Schema;
|
current: Schema;
|
||||||
api?: APIClient;
|
api?: APIClient;
|
||||||
refresh?: () => void;
|
refresh?: () => void;
|
||||||
|
onSuccess?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createDesignable(options: CreateDesignableProps) {
|
export function createDesignable(options: CreateDesignableProps) {
|
||||||
@ -26,6 +27,7 @@ interface InsertAdjacentOptions {
|
|||||||
wrap?: (s: ISchema) => ISchema;
|
wrap?: (s: ISchema) => ISchema;
|
||||||
removeParentsIfNoChildren?: boolean;
|
removeParentsIfNoChildren?: boolean;
|
||||||
breakRemoveOn?: ISchema | BreakFn;
|
breakRemoveOn?: ISchema | BreakFn;
|
||||||
|
onSuccess?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
type BreakFn = (s: ISchema) => boolean;
|
type BreakFn = (s: ISchema) => boolean;
|
||||||
@ -80,7 +82,7 @@ export class Designable {
|
|||||||
if (!api) {
|
if (!api) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.on('insertAdjacent', async ({ current, position, schema, removed }) => {
|
this.on('insertAdjacent', async ({ onSuccess, current, position, schema, removed }) => {
|
||||||
refresh();
|
refresh();
|
||||||
await api.request({
|
await api.request({
|
||||||
url: `/uiSchemas:insertAdjacent/${current['x-uid']}?position=${position}`,
|
url: `/uiSchemas:insertAdjacent/${current['x-uid']}?position=${position}`,
|
||||||
@ -93,6 +95,7 @@ export class Designable {
|
|||||||
method: 'post',
|
method: 'post',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
onSuccess?.();
|
||||||
message.success('配置保存成功!', 0.2);
|
message.success('配置保存成功!', 0.2);
|
||||||
});
|
});
|
||||||
this.on('patch', async ({ schema }) => {
|
this.on('patch', async ({ schema }) => {
|
||||||
@ -229,6 +232,19 @@ export class Designable {
|
|||||||
this.emit('remove', { removed });
|
this.emit('remove', { removed });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeWithoutEmit(schema?: Schema, options: RemoveOptions = {}) {
|
||||||
|
const { breakRemoveOn, removeParentsIfNoChildren } = options;
|
||||||
|
let s = schema || this.current;
|
||||||
|
let removed = s.parent.removeProperty(s.name);
|
||||||
|
if (removeParentsIfNoChildren) {
|
||||||
|
const parent = this.recursiveRemoveIfNoChildren(s.parent, { breakRemoveOn });
|
||||||
|
if (parent) {
|
||||||
|
removed = parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
insertBeforeBeginOrAfterEnd(schema: ISchema, options: InsertAdjacentOptions = {}) {
|
insertBeforeBeginOrAfterEnd(schema: ISchema, options: InsertAdjacentOptions = {}) {
|
||||||
if (!Schema.isSchemaInstance(this.current)) {
|
if (!Schema.isSchemaInstance(this.current)) {
|
||||||
return;
|
return;
|
||||||
@ -393,7 +409,7 @@ export class Designable {
|
|||||||
if (!Schema.isSchemaInstance(this.current)) {
|
if (!Schema.isSchemaInstance(this.current)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const opts = {};
|
const opts = { onSuccess: options?.onSuccess };
|
||||||
const { wrap = defaultWrap, breakRemoveOn, removeParentsIfNoChildren } = options;
|
const { wrap = defaultWrap, breakRemoveOn, removeParentsIfNoChildren } = options;
|
||||||
if (Schema.isSchemaInstance(schema)) {
|
if (Schema.isSchemaInstance(schema)) {
|
||||||
if (this.parentsIn(schema)) {
|
if (this.parentsIn(schema)) {
|
||||||
|
@ -5,7 +5,9 @@ import React, { useContext } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useCollectionManager } from '../../../collection-manager';
|
import { useCollectionManager } from '../../../collection-manager';
|
||||||
import { SchemaComponent, SchemaComponentOptions } from '../../../schema-component';
|
import { SchemaComponent, SchemaComponentOptions } from '../../../schema-component';
|
||||||
|
import { useSchemaTemplateManager } from '../../../schema-templates';
|
||||||
import { SchemaInitializer } from '../../SchemaInitializer';
|
import { SchemaInitializer } from '../../SchemaInitializer';
|
||||||
|
import { useCollectionDataSourceItems } from '../utils';
|
||||||
|
|
||||||
const createSchema = (collectionName, { title, start, end }) => {
|
const createSchema = (collectionName, { title, start, end }) => {
|
||||||
const schema: ISchema = {
|
const schema: ISchema = {
|
||||||
@ -102,11 +104,17 @@ export const CalendarBlockInitializer = (props) => {
|
|||||||
const { collections, getCollection } = useCollectionManager();
|
const { collections, getCollection } = useCollectionManager();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const options = useContext(SchemaOptionsContext);
|
const options = useContext(SchemaOptionsContext);
|
||||||
|
const { getTemplateSchemaByMode } = useSchemaTemplateManager();
|
||||||
return (
|
return (
|
||||||
<SchemaInitializer.Item
|
<SchemaInitializer.Item
|
||||||
{...props}
|
{...props}
|
||||||
icon={<FormOutlined />}
|
icon={<FormOutlined />}
|
||||||
onClick={async ({ item }) => {
|
onClick={async ({ item }) => {
|
||||||
|
if (item.template) {
|
||||||
|
const s = await getTemplateSchemaByMode(item);
|
||||||
|
insert(s);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const collection = getCollection(item.name);
|
const collection = getCollection(item.name);
|
||||||
const stringFields = collection?.fields
|
const stringFields = collection?.fields
|
||||||
?.filter((field) => field.type === 'string')
|
?.filter((field) => field.type === 'string')
|
||||||
@ -161,23 +169,10 @@ export const CalendarBlockInitializer = (props) => {
|
|||||||
}).open({
|
}).open({
|
||||||
initialValues: {},
|
initialValues: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
insert(createSchema(item.name, values));
|
insert(createSchema(item.name, values));
|
||||||
}}
|
}}
|
||||||
items={[
|
items={useCollectionDataSourceItems('Calendar')}
|
||||||
{
|
|
||||||
type: 'itemGroup',
|
|
||||||
title: t('Select data source'),
|
|
||||||
children: collections
|
|
||||||
?.filter((item) => !item.inherit)
|
|
||||||
?.map((item) => {
|
|
||||||
return {
|
|
||||||
type: 'item',
|
|
||||||
name: item.name,
|
|
||||||
title: item.title,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -4,6 +4,8 @@ import React from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { SchemaInitializer } from '../..';
|
import { SchemaInitializer } from '../..';
|
||||||
import { useCollectionManager } from '../../../collection-manager';
|
import { useCollectionManager } from '../../../collection-manager';
|
||||||
|
import { useSchemaTemplateManager } from '../../../schema-templates';
|
||||||
|
import { useCollectionDataSourceItems } from '../utils';
|
||||||
|
|
||||||
const createSchema = (collectionName) => {
|
const createSchema = (collectionName) => {
|
||||||
const schema: ISchema = {
|
const schema: ISchema = {
|
||||||
@ -55,28 +57,20 @@ export const FormBlockInitializer = (props) => {
|
|||||||
const { insert } = props;
|
const { insert } = props;
|
||||||
const { collections } = useCollectionManager();
|
const { collections } = useCollectionManager();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { getTemplateSchemaByMode } = useSchemaTemplateManager();
|
||||||
return (
|
return (
|
||||||
<SchemaInitializer.Item
|
<SchemaInitializer.Item
|
||||||
{...props}
|
{...props}
|
||||||
icon={<FormOutlined />}
|
icon={<FormOutlined />}
|
||||||
onClick={({ item }) => {
|
onClick={async ({ item }) => {
|
||||||
|
if (item.template) {
|
||||||
|
const s = await getTemplateSchemaByMode(item);
|
||||||
|
insert(s);
|
||||||
|
} else {
|
||||||
insert(createSchema(item.name));
|
insert(createSchema(item.name));
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
items={[
|
items={useCollectionDataSourceItems('Form')}
|
||||||
{
|
|
||||||
type: 'itemGroup',
|
|
||||||
title: t('Select data source'),
|
|
||||||
children: collections
|
|
||||||
?.filter((item) => !item.inherit)
|
|
||||||
?.map((item) => {
|
|
||||||
return {
|
|
||||||
type: 'item',
|
|
||||||
name: item.name,
|
|
||||||
title: item.title,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -6,7 +6,9 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useAPIClient } from '../../../api-client';
|
import { useAPIClient } from '../../../api-client';
|
||||||
import { useCollectionManager } from '../../../collection-manager';
|
import { useCollectionManager } from '../../../collection-manager';
|
||||||
import { SchemaComponent, SchemaComponentOptions } from '../../../schema-component';
|
import { SchemaComponent, SchemaComponentOptions } from '../../../schema-component';
|
||||||
|
import { useSchemaTemplateManager } from '../../../schema-templates';
|
||||||
import { SchemaInitializer } from '../../SchemaInitializer';
|
import { SchemaInitializer } from '../../SchemaInitializer';
|
||||||
|
import { useCollectionDataSourceItems } from '../utils';
|
||||||
|
|
||||||
const createSchema = (collectionName, { groupField, sortName }) => {
|
const createSchema = (collectionName, { groupField, sortName }) => {
|
||||||
const schema: ISchema = {
|
const schema: ISchema = {
|
||||||
@ -158,6 +160,7 @@ export const KanbanBlockInitializer = (props) => {
|
|||||||
const { insert } = props;
|
const { insert } = props;
|
||||||
const { collections, getCollection } = useCollectionManager();
|
const { collections, getCollection } = useCollectionManager();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { getTemplateSchemaByMode } = useSchemaTemplateManager();
|
||||||
const options = useContext(SchemaOptionsContext);
|
const options = useContext(SchemaOptionsContext);
|
||||||
const api = useAPIClient();
|
const api = useAPIClient();
|
||||||
return (
|
return (
|
||||||
@ -165,6 +168,11 @@ export const KanbanBlockInitializer = (props) => {
|
|||||||
{...props}
|
{...props}
|
||||||
icon={<FormOutlined />}
|
icon={<FormOutlined />}
|
||||||
onClick={async ({ item }) => {
|
onClick={async ({ item }) => {
|
||||||
|
if (item.template) {
|
||||||
|
const s = await getTemplateSchemaByMode(item);
|
||||||
|
insert(s);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const collection = getCollection(item.name);
|
const collection = getCollection(item.name);
|
||||||
const fields = collection?.fields
|
const fields = collection?.fields
|
||||||
?.filter((field) => ['select', 'radioGroup'].includes(field.interface))
|
?.filter((field) => ['select', 'radioGroup'].includes(field.interface))
|
||||||
@ -219,21 +227,7 @@ export const KanbanBlockInitializer = (props) => {
|
|||||||
}
|
}
|
||||||
insert(createSchema(item.name, { ...values, sortName }));
|
insert(createSchema(item.name, { ...values, sortName }));
|
||||||
}}
|
}}
|
||||||
items={[
|
items={useCollectionDataSourceItems('Kanban')}
|
||||||
{
|
|
||||||
type: 'itemGroup',
|
|
||||||
title: t('Select data source'),
|
|
||||||
children: collections
|
|
||||||
?.filter((item) => !item.inherit)
|
|
||||||
?.map((item) => {
|
|
||||||
return {
|
|
||||||
type: 'item',
|
|
||||||
name: item.name,
|
|
||||||
title: item.title,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { TableOutlined } from '@ant-design/icons';
|
import { TableOutlined } from '@ant-design/icons';
|
||||||
import { ISchema } from '@formily/react';
|
import { ISchema } from '@formily/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { SchemaInitializer } from '../..';
|
import { SchemaInitializer } from '../..';
|
||||||
import { useCollectionManager } from '../../../collection-manager';
|
import { useSchemaTemplateManager } from '../../../schema-templates';
|
||||||
|
import { useCollectionDataSourceItems } from '../utils';
|
||||||
|
|
||||||
const createSchema = (collectionName) => {
|
export const createTableBlockSchema = (collectionName) => {
|
||||||
const schema: ISchema = {
|
const schema: ISchema = {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
'x-collection': 'collections',
|
'x-collection': 'collections',
|
||||||
@ -79,30 +79,20 @@ const createSchema = (collectionName) => {
|
|||||||
|
|
||||||
export const TableBlockInitializer = (props) => {
|
export const TableBlockInitializer = (props) => {
|
||||||
const { insert } = props;
|
const { insert } = props;
|
||||||
const { collections } = useCollectionManager();
|
const { getTemplateSchemaByMode } = useSchemaTemplateManager();
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
return (
|
||||||
<SchemaInitializer.Item
|
<SchemaInitializer.Item
|
||||||
{...props}
|
{...props}
|
||||||
icon={<TableOutlined />}
|
icon={<TableOutlined />}
|
||||||
onClick={({ item }) => {
|
onClick={async ({ item }) => {
|
||||||
insert(createSchema(item.name));
|
if (item.template) {
|
||||||
|
const s = await getTemplateSchemaByMode(item);
|
||||||
|
insert(s);
|
||||||
|
} else {
|
||||||
|
insert(createTableBlockSchema(item.name));
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
items={[
|
items={useCollectionDataSourceItems('Table')}
|
||||||
{
|
|
||||||
type: 'itemGroup',
|
|
||||||
title: t('Select data source'),
|
|
||||||
children: collections
|
|
||||||
?.filter((item) => !item.inherit)
|
|
||||||
?.map((item) => {
|
|
||||||
return {
|
|
||||||
type: 'item',
|
|
||||||
name: item.name,
|
|
||||||
title: item.title,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { ISchema, Schema, useFieldSchema } from '@formily/react';
|
import { ISchema, Schema, useFieldSchema } from '@formily/react';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { SchemaInitializerItemOptions } from '../';
|
import { SchemaInitializerItemOptions } from '../';
|
||||||
import { useCollection } from '../../collection-manager';
|
import { useCollection, useCollectionManager } from '../../collection-manager';
|
||||||
import { useDesignable } from '../../schema-component';
|
import { useDesignable } from '../../schema-component';
|
||||||
|
import { useSchemaTemplateManager } from '../../schema-templates';
|
||||||
|
|
||||||
export const gridRowColWrap = (schema: ISchema) => {
|
export const gridRowColWrap = (schema: ISchema) => {
|
||||||
return {
|
return {
|
||||||
@ -203,3 +205,76 @@ export const useCurrentSchema = (action: string, key: string, find = findSchema,
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useCollectionDataSourceItems = (componentName) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { collections } = useCollectionManager();
|
||||||
|
const { getTemplatesByCollection } = useSchemaTemplateManager();
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'tableBlock',
|
||||||
|
type: 'itemGroup',
|
||||||
|
title: t('Select data source'),
|
||||||
|
children: collections
|
||||||
|
?.filter((item) => !item.inherit)
|
||||||
|
?.map((item, index) => {
|
||||||
|
const templates = getTemplatesByCollection(item.name).filter((template) => {
|
||||||
|
return componentName && template.componentName === componentName;
|
||||||
|
});
|
||||||
|
if (!templates.length) {
|
||||||
|
return {
|
||||||
|
type: 'item',
|
||||||
|
name: item.name,
|
||||||
|
title: item.title,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
key: `${componentName}_table_subMenu_${index}`,
|
||||||
|
type: 'subMenu',
|
||||||
|
name: `${item.name}_${index}`,
|
||||||
|
title: item.title,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'item',
|
||||||
|
name: item.name,
|
||||||
|
title: '空白区块',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'divider',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: `${componentName}_table_subMenu_${index}_copy`,
|
||||||
|
type: 'subMenu',
|
||||||
|
name: 'copy',
|
||||||
|
title: '复制模板',
|
||||||
|
children: templates.map((template) => {
|
||||||
|
return {
|
||||||
|
type: 'item',
|
||||||
|
mode: 'copy',
|
||||||
|
name: item.name,
|
||||||
|
template,
|
||||||
|
title: template.name || '未命名',
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: `${componentName}_table_subMenu_${index}_ref`,
|
||||||
|
type: 'subMenu',
|
||||||
|
name: 'ref',
|
||||||
|
title: '引用模板',
|
||||||
|
children: templates.map((template) => {
|
||||||
|
return {
|
||||||
|
type: 'item',
|
||||||
|
mode: 'reference',
|
||||||
|
name: item.name,
|
||||||
|
template,
|
||||||
|
title: template.name || '未命名',
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
@ -42,11 +42,10 @@ SchemaInitializer.Button = observer((props: SchemaInitializerButtonProps) => {
|
|||||||
insertAdjacent(insertPosition, wrap(schema));
|
insertAdjacent(insertPosition, wrap(schema));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const menu = (
|
const renderItems = (items: any) => {
|
||||||
<Menu>
|
return items?.map((item, indexA) => {
|
||||||
{items?.map((item, indexA) => {
|
|
||||||
if (item.type === 'divider') {
|
if (item.type === 'divider') {
|
||||||
return <Menu.Divider key={`item-${indexA}`} />;
|
return <Menu.Divider key={item.key || `item-${indexA}`} />;
|
||||||
}
|
}
|
||||||
if (item.type === 'item' && item.component) {
|
if (item.type === 'item' && item.component) {
|
||||||
const Component = findComponent(item.component);
|
const Component = findComponent(item.component);
|
||||||
@ -73,45 +72,26 @@ SchemaInitializer.Button = observer((props: SchemaInitializerButtonProps) => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (item.type === 'itemGroup') {
|
||||||
return (
|
return (
|
||||||
item?.children?.length > 0 && (
|
<Menu.ItemGroup key={item.key || `item-group-${indexA}`} title={compile(item.title)}>
|
||||||
<Menu.ItemGroup key={`item-group-${indexA}`} title={compile(item.title)}>
|
{renderItems(item.children)}
|
||||||
{item?.children?.map((child, indexB) => {
|
|
||||||
if (!child.component) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const Component = findComponent(child.component);
|
|
||||||
return (
|
|
||||||
Component && (
|
|
||||||
<SchemaInitializerItemContext.Provider
|
|
||||||
key={`item-${indexB}`}
|
|
||||||
value={{
|
|
||||||
index: indexB,
|
|
||||||
item: child,
|
|
||||||
info: child,
|
|
||||||
insert: insertSchema,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Component
|
|
||||||
{...child}
|
|
||||||
item={{
|
|
||||||
...child,
|
|
||||||
title: compile(child.title),
|
|
||||||
}}
|
|
||||||
insert={insertSchema}
|
|
||||||
/>
|
|
||||||
</SchemaInitializerItemContext.Provider>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Menu.ItemGroup>
|
</Menu.ItemGroup>
|
||||||
)
|
|
||||||
);
|
);
|
||||||
})}
|
}
|
||||||
</Menu>
|
if (item.type === 'subMenu') {
|
||||||
|
return (
|
||||||
|
<Menu.SubMenu key={item.key || `item-group-${indexA}`} title={compile(item.title)}>
|
||||||
|
{renderItems(item.children)}
|
||||||
|
</Menu.SubMenu>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
if (!designable) {
|
const menu = <Menu>{renderItems(items)}</Menu>;
|
||||||
|
|
||||||
|
if (!designable && props.designable !== true) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,15 +104,17 @@ SchemaInitializer.Button = observer((props: SchemaInitializerButtonProps) => {
|
|||||||
{...dropdown}
|
{...dropdown}
|
||||||
overlay={menu}
|
overlay={menu}
|
||||||
>
|
>
|
||||||
{component ? component : (
|
{component ? (
|
||||||
|
component
|
||||||
|
) : (
|
||||||
<Button
|
<Button
|
||||||
{...others}
|
|
||||||
type={'dashed'}
|
type={'dashed'}
|
||||||
style={{
|
style={{
|
||||||
...style,
|
|
||||||
borderColor: '#f18b62',
|
borderColor: '#f18b62',
|
||||||
color: '#f18b62',
|
color: '#f18b62',
|
||||||
|
...style,
|
||||||
}}
|
}}
|
||||||
|
{...others}
|
||||||
>
|
>
|
||||||
{compile(props.children || props.title)}
|
{compile(props.children || props.title)}
|
||||||
</Button>
|
</Button>
|
||||||
@ -156,16 +138,24 @@ SchemaInitializer.Item = (props: SchemaInitializerItemProps) => {
|
|||||||
}
|
}
|
||||||
if (item.type === 'itemGroup') {
|
if (item.type === 'itemGroup') {
|
||||||
return (
|
return (
|
||||||
|
<Menu.ItemGroup
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
<Menu.ItemGroup eventKey={`item-group-${indexA}`} key={`item-group-${indexA}`} title={compile(item.title)}>
|
eventKey={item.key || `item-group-${indexA}`}
|
||||||
|
key={item.key || `item-group-${indexA}`}
|
||||||
|
title={compile(item.title)}
|
||||||
|
>
|
||||||
{renderMenuItem(item.children)}
|
{renderMenuItem(item.children)}
|
||||||
</Menu.ItemGroup>
|
</Menu.ItemGroup>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (item.type === 'subMenu') {
|
if (item.type === 'subMenu') {
|
||||||
return (
|
return (
|
||||||
|
<Menu.SubMenu
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
<Menu.SubMenu eventKey={`sub-menu-${indexA}`} key={`sub-menu-${indexA}`} title={compile(item.title)}>
|
eventKey={item.key || `sub-menu-${indexA}`}
|
||||||
|
key={item.key || `sub-menu-${indexA}`}
|
||||||
|
title={compile(item.title)}
|
||||||
|
>
|
||||||
{renderMenuItem(item.children)}
|
{renderMenuItem(item.children)}
|
||||||
</Menu.SubMenu>
|
</Menu.SubMenu>
|
||||||
);
|
);
|
||||||
|
@ -8,12 +8,14 @@ export interface SchemaInitializerButtonProps extends ButtonProps {
|
|||||||
items?: SchemaInitializerItemOptions[];
|
items?: SchemaInitializerItemOptions[];
|
||||||
dropdown?: DropDownProps;
|
dropdown?: DropDownProps;
|
||||||
component?: any;
|
component?: any;
|
||||||
|
designable?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SchemaInitializerItemOptions = ItemGroupOptions | SubMenuOptions | ItemOptions | DividerOptions;
|
export type SchemaInitializerItemOptions = ItemGroupOptions | SubMenuOptions | ItemOptions | DividerOptions;
|
||||||
|
|
||||||
interface ItemCommonOptions {
|
interface ItemCommonOptions {
|
||||||
title?: any;
|
title?: any;
|
||||||
|
key?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ItemGroupOptions extends ItemCommonOptions {
|
interface ItemGroupOptions extends ItemCommonOptions {
|
||||||
|
@ -11,19 +11,26 @@ const titleCss = css`
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
background: #f18b62;
|
/* background: #f18b62;
|
||||||
color: #fff;
|
color: #fff; */
|
||||||
padding: 0 5px;
|
padding: 0;
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
border-bottom-right-radius: 2px;
|
border-bottom-right-radius: 2px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
top: 2px;
|
top: 2px;
|
||||||
left: 2px;
|
left: 2px;
|
||||||
|
.title-tag {
|
||||||
|
padding: 0 3px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: #f18b62;
|
||||||
|
color: #fff;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const GeneralSchemaDesigner = (props: any) => {
|
export const GeneralSchemaDesigner = (props: any) => {
|
||||||
const { title, draggable = true } = props;
|
const { title, template, draggable = true } = props;
|
||||||
const { dn, designable } = useDesignable();
|
const { dn, designable } = useDesignable();
|
||||||
const field = useField();
|
const field = useField();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
@ -38,7 +45,14 @@ export const GeneralSchemaDesigner = (props: any) => {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className={'general-schema-designer'}>
|
<div className={'general-schema-designer'}>
|
||||||
{title && <div className={classNames('general-schema-designer-title', titleCss)}>{compile(title)}</div>}
|
{title && (
|
||||||
|
<div className={classNames('general-schema-designer-title', titleCss)}>
|
||||||
|
<Space size={2}>
|
||||||
|
<span className={'title-tag'}>{compile(title)}</span>
|
||||||
|
{template && <span className={'title-tag'}>引用模板: {template?.name || '未命名'}</span>}
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className={'general-schema-designer-icons'}>
|
<div className={'general-schema-designer-icons'}>
|
||||||
<Space size={2} align={'center'}>
|
<Space size={2} align={'center'}>
|
||||||
{draggable && (
|
{draggable && (
|
||||||
|
@ -1,11 +1,22 @@
|
|||||||
import { FormDialog, FormLayout } from '@formily/antd';
|
import { FormDialog, FormItem, FormLayout, Input } from '@formily/antd';
|
||||||
import { GeneralField } from '@formily/core';
|
import { GeneralField } from '@formily/core';
|
||||||
import { ISchema, Schema, SchemaOptionsContext } from '@formily/react';
|
import { ISchema, Schema, SchemaOptionsContext } from '@formily/react';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import { Dropdown, Menu, MenuItemProps, Modal, Select, Switch } from 'antd';
|
import { Dropdown, Menu, MenuItemProps, Modal, Select, Switch } from 'antd';
|
||||||
import React, { createContext, useContext, useState } from 'react';
|
import React, { createContext, useContext, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ActionContext, Designable, SchemaComponent, SchemaComponentOptions, useActionContext } from '..';
|
import {
|
||||||
|
ActionContext,
|
||||||
|
createDesignable,
|
||||||
|
Designable,
|
||||||
|
SchemaComponent,
|
||||||
|
SchemaComponentOptions,
|
||||||
|
useActionContext,
|
||||||
|
useAPIClient,
|
||||||
|
useCollection
|
||||||
|
} from '..';
|
||||||
|
import { useSchemaTemplateManager } from '../schema-templates';
|
||||||
|
import { useBlockTemplateContext } from '../schema-templates/BlockTemplate';
|
||||||
|
|
||||||
interface SchemaSettingsProps {
|
interface SchemaSettingsProps {
|
||||||
title?: any;
|
title?: any;
|
||||||
@ -20,6 +31,8 @@ interface SchemaSettingsContextProps {
|
|||||||
fieldSchema?: Schema;
|
fieldSchema?: Schema;
|
||||||
setVisible?: any;
|
setVisible?: any;
|
||||||
visible?: any;
|
visible?: any;
|
||||||
|
template?: any;
|
||||||
|
collectionName?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SchemaSettingsContext = createContext<SchemaSettingsContextProps>(null);
|
const SchemaSettingsContext = createContext<SchemaSettingsContextProps>(null);
|
||||||
@ -48,19 +61,24 @@ interface SchemaSettingsProviderProps {
|
|||||||
fieldSchema?: Schema;
|
fieldSchema?: Schema;
|
||||||
setVisible?: any;
|
setVisible?: any;
|
||||||
visible?: any;
|
visible?: any;
|
||||||
|
template?: any;
|
||||||
|
collectionName?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SchemaSettingsProvider: React.FC<SchemaSettingsProviderProps> = (props) => {
|
export const SchemaSettingsProvider: React.FC<SchemaSettingsProviderProps> = (props) => {
|
||||||
const { visible, setVisible, dn, field, fieldSchema, children } = props;
|
const { children, fieldSchema, ...others } = props;
|
||||||
|
const { getTemplateBySchemaId } = useSchemaTemplateManager();
|
||||||
|
const { name } = useCollection();
|
||||||
|
const template = getTemplateBySchemaId(fieldSchema['x-uid']);
|
||||||
return (
|
return (
|
||||||
<SchemaSettingsContext.Provider value={{ visible, setVisible, dn, field, fieldSchema }}>
|
<SchemaSettingsContext.Provider value={{ collectionName: name, template, fieldSchema, ...others }}>
|
||||||
{children}
|
{children}
|
||||||
</SchemaSettingsContext.Provider>
|
</SchemaSettingsContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SchemaSettings: React.FC<SchemaSettingsProps> & SchemaSettingsNested = (props) => {
|
export const SchemaSettings: React.FC<SchemaSettingsProps> & SchemaSettingsNested = (props) => {
|
||||||
const { title, dn, field, fieldSchema } = props;
|
const { title, dn, ...others } = props;
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const DropdownMenu = (
|
const DropdownMenu = (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
@ -75,7 +93,7 @@ export const SchemaSettings: React.FC<SchemaSettingsProps> & SchemaSettingsNeste
|
|||||||
);
|
);
|
||||||
if (dn) {
|
if (dn) {
|
||||||
return (
|
return (
|
||||||
<SchemaSettingsProvider visible={visible} setVisible={setVisible} dn={dn} field={field} fieldSchema={fieldSchema}>
|
<SchemaSettingsProvider visible={visible} setVisible={setVisible} dn={dn} {...others}>
|
||||||
{DropdownMenu}
|
{DropdownMenu}
|
||||||
</SchemaSettingsProvider>
|
</SchemaSettingsProvider>
|
||||||
);
|
);
|
||||||
@ -83,6 +101,86 @@ export const SchemaSettings: React.FC<SchemaSettingsProps> & SchemaSettingsNeste
|
|||||||
return DropdownMenu;
|
return DropdownMenu;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SchemaSettings.Template = (props) => {
|
||||||
|
const { componentName, collectionName } = props;
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { dn, setVisible, template, fieldSchema } = useSchemaSettings();
|
||||||
|
const api = useAPIClient();
|
||||||
|
const { dn: tdn } = useBlockTemplateContext();
|
||||||
|
const { saveAsTemplate, copyTemplateSchema } = useSchemaTemplateManager();
|
||||||
|
if (!collectionName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (template) {
|
||||||
|
return (
|
||||||
|
<SchemaSettings.Item
|
||||||
|
onClick={async () => {
|
||||||
|
const schema = await copyTemplateSchema(template);
|
||||||
|
const removed = tdn.removeWithoutEmit();
|
||||||
|
tdn.insertAfterEnd(schema, {
|
||||||
|
async onSuccess() {
|
||||||
|
await api.request({
|
||||||
|
url: `/uiSchemas:remove/${removed['x-uid']}`,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Convert reference to duplicate')}
|
||||||
|
</SchemaSettings.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<SchemaSettings.Item
|
||||||
|
onClick={async () => {
|
||||||
|
setVisible(false);
|
||||||
|
const values = await FormDialog('Save as template', () => {
|
||||||
|
return (
|
||||||
|
<FormLayout layout={'vertical'}>
|
||||||
|
<SchemaComponent
|
||||||
|
components={{ Input, FormItem }}
|
||||||
|
schema={{
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
title: '模板名称',
|
||||||
|
required: true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormLayout>
|
||||||
|
);
|
||||||
|
}).open({});
|
||||||
|
const sdn = createDesignable({
|
||||||
|
api,
|
||||||
|
refresh: dn.refresh.bind(dn),
|
||||||
|
current: fieldSchema.parent,
|
||||||
|
});
|
||||||
|
sdn.loadAPIClientEvents();
|
||||||
|
const { key } = await saveAsTemplate({
|
||||||
|
collectionName,
|
||||||
|
componentName,
|
||||||
|
name: values.name,
|
||||||
|
uid: fieldSchema['x-uid'],
|
||||||
|
});
|
||||||
|
sdn.removeWithoutEmit(fieldSchema);
|
||||||
|
sdn.insertBeforeEnd({
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'BlockTemplate',
|
||||||
|
'x-component-props': {
|
||||||
|
templateId: key,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Save as template')}
|
||||||
|
</SchemaSettings.Item>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
SchemaSettings.Item = (props) => {
|
SchemaSettings.Item = (props) => {
|
||||||
const { eventKey } = props;
|
const { eventKey } = props;
|
||||||
return (
|
return (
|
||||||
@ -116,8 +214,9 @@ SchemaSettings.Divider = (props) => {
|
|||||||
|
|
||||||
SchemaSettings.Remove = (props: any) => {
|
SchemaSettings.Remove = (props: any) => {
|
||||||
const { confirm, removeParentsIfNoChildren, breakRemoveOn } = props;
|
const { confirm, removeParentsIfNoChildren, breakRemoveOn } = props;
|
||||||
const { dn } = useSchemaSettings();
|
const { dn, template } = useSchemaSettings();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const ctx = useBlockTemplateContext();
|
||||||
return (
|
return (
|
||||||
<SchemaSettings.Item
|
<SchemaSettings.Item
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -126,10 +225,15 @@ SchemaSettings.Remove = (props: any) => {
|
|||||||
content: t('Are you sure you want to delete it?'),
|
content: t('Are you sure you want to delete it?'),
|
||||||
...confirm,
|
...confirm,
|
||||||
onOk() {
|
onOk() {
|
||||||
dn.remove(null, {
|
const options = {
|
||||||
removeParentsIfNoChildren,
|
removeParentsIfNoChildren,
|
||||||
breakRemoveOn,
|
breakRemoveOn,
|
||||||
});
|
};
|
||||||
|
if (template && ctx?.dn) {
|
||||||
|
ctx?.dn.remove(null, options);
|
||||||
|
} else {
|
||||||
|
dn.remove(null, options);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
59
packages/client/src/schema-templates/AddBlockTemplate.tsx
Normal file
59
packages/client/src/schema-templates/AddBlockTemplate.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { uid } from '@formily/shared';
|
||||||
|
import { Button, Dropdown, Menu } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import { useAPIClient, useCollectionManager, useCompile, useSchemaTemplateManager } from '..';
|
||||||
|
import { createTableBlockSchema } from '../schema-initializer/Initializers/Items';
|
||||||
|
|
||||||
|
export const AddBlockTemplate = (props: any) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const history = useHistory();
|
||||||
|
const api = useAPIClient();
|
||||||
|
const { refresh } = useSchemaTemplateManager();
|
||||||
|
const { collections } = useCollectionManager();
|
||||||
|
const compile = useCompile();
|
||||||
|
const insert = ({ uiSchema, collectionName }) => {
|
||||||
|
const key = uid();
|
||||||
|
api
|
||||||
|
.resource('uiSchemaTemplates')
|
||||||
|
.create({
|
||||||
|
values: {
|
||||||
|
key,
|
||||||
|
collectionName,
|
||||||
|
uiSchema,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
refresh();
|
||||||
|
history.push(`/admin/block-templates/${key}`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dropdown
|
||||||
|
overlay={
|
||||||
|
<Menu>
|
||||||
|
<Menu.SubMenu title={'Table'}>
|
||||||
|
{collections?.map((collection) => {
|
||||||
|
return (
|
||||||
|
<Menu.Item
|
||||||
|
key={collection.name}
|
||||||
|
onClick={() => {
|
||||||
|
const uiSchema = createTableBlockSchema(collection.name);
|
||||||
|
insert({ uiSchema, collectionName: collection.name });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{compile(collection.title)}
|
||||||
|
</Menu.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Menu.SubMenu>
|
||||||
|
</Menu>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button type={'primary'}>{t('Add block template')}</Button>
|
||||||
|
</Dropdown>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
26
packages/client/src/schema-templates/BlockTemplate.tsx
Normal file
26
packages/client/src/schema-templates/BlockTemplate.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { observer, useField, useFieldSchema } from '@formily/react';
|
||||||
|
import React, { createContext, useContext, useMemo } from 'react';
|
||||||
|
import { RemoteSchemaComponent, useDesignable } from '..';
|
||||||
|
import { useSchemaTemplateManager } from './SchemaTemplateManagerProvider';
|
||||||
|
|
||||||
|
const BlockTemplateContext = createContext<any>({});
|
||||||
|
|
||||||
|
export const useBlockTemplateContext = () => {
|
||||||
|
return useContext(BlockTemplateContext);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BlockTemplate = observer((props: any) => {
|
||||||
|
const { templateId } = props;
|
||||||
|
const { getTemplateById } = useSchemaTemplateManager();
|
||||||
|
const field = useField();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { dn } = useDesignable();
|
||||||
|
const template = useMemo(() => getTemplateById(templateId), [templateId]);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<BlockTemplateContext.Provider value={{ dn, field, fieldSchema, template }}>
|
||||||
|
<RemoteSchemaComponent uid={template?.uid} />
|
||||||
|
</BlockTemplateContext.Provider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
@ -0,0 +1,91 @@
|
|||||||
|
import { Input, PageHeader as AntdPageHeader, Spin } from 'antd';
|
||||||
|
import React, { useContext, useState } from 'react';
|
||||||
|
import { useHistory, useRouteMatch } from 'react-router-dom';
|
||||||
|
import { useAPIClient, useRequest, useSchemaTemplateManager } from '..';
|
||||||
|
import { RemoteSchemaComponent, SchemaComponentContext } from '../schema-component';
|
||||||
|
|
||||||
|
const EditableTitle = (props) => {
|
||||||
|
const [title, setTitle] = useState(props.title);
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const { refresh } = useSchemaTemplateManager();
|
||||||
|
const api = useAPIClient();
|
||||||
|
const { run } = useRequest(
|
||||||
|
{
|
||||||
|
resource: 'uiSchemaTemplates',
|
||||||
|
action: 'update',
|
||||||
|
params: {
|
||||||
|
filterByTk: props.filterByTk,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
manual: true,
|
||||||
|
debounceWait: 500,
|
||||||
|
onSuccess() {
|
||||||
|
refresh();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{visible ? (
|
||||||
|
<Input
|
||||||
|
defaultValue={title}
|
||||||
|
size={'large'}
|
||||||
|
onBlur={() => {
|
||||||
|
setVisible(false);
|
||||||
|
}}
|
||||||
|
onChange={(e) => {
|
||||||
|
setTitle(e.target.value);
|
||||||
|
run({
|
||||||
|
filterByTk: props.filterByTk,
|
||||||
|
values: {
|
||||||
|
name: e.target.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
setVisible(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{title || <span style={{ color: '#bbb' }}>未命名</span>}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BlockTemplateDetails = () => {
|
||||||
|
const history = useHistory();
|
||||||
|
const match = useRouteMatch<any>();
|
||||||
|
const key = match?.params?.key;
|
||||||
|
const value = useContext(SchemaComponentContext);
|
||||||
|
const { data, loading } = useRequest({
|
||||||
|
resource: 'uiSchemaTemplates',
|
||||||
|
action: 'get',
|
||||||
|
params: {
|
||||||
|
filterByTk: key,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (loading) {
|
||||||
|
return <Spin />;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<AntdPageHeader
|
||||||
|
onBack={() => {
|
||||||
|
history.push('/admin/block-templates');
|
||||||
|
}}
|
||||||
|
ghost={false}
|
||||||
|
title={<EditableTitle filterByTk={key} title={data?.data?.name} />}
|
||||||
|
/>
|
||||||
|
<div style={{ margin: 24 }}>
|
||||||
|
<SchemaComponentContext.Provider value={{ ...value, designable: true }}>
|
||||||
|
<RemoteSchemaComponent uid={data?.data?.uid} />
|
||||||
|
</SchemaComponentContext.Provider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
56
packages/client/src/schema-templates/BlockTemplatePage.tsx
Normal file
56
packages/client/src/schema-templates/BlockTemplatePage.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { Card, PageHeader as AntdPageHeader, Table } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { useCompile, useRequest } from '..';
|
||||||
|
|
||||||
|
export const BlockTemplatePage = () => {
|
||||||
|
const { data, loading } = useRequest({
|
||||||
|
resource: 'uiSchemaTemplates',
|
||||||
|
action: 'list',
|
||||||
|
params: {
|
||||||
|
appends: ['collection'],
|
||||||
|
sort: ['-createdAt'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const compile = useCompile();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<AntdPageHeader ghost={false} title={t('Block templates')} />
|
||||||
|
<div style={{ margin: 24 }}>
|
||||||
|
<Card bordered={false}>
|
||||||
|
<Table
|
||||||
|
rowSelection={{
|
||||||
|
type: 'checkbox',
|
||||||
|
}}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
dataIndex: 'name',
|
||||||
|
title: t('Template name'),
|
||||||
|
render: (value) => <>{value || '未命名'}</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: ['collection', 'title'],
|
||||||
|
title: t('Collection display name'),
|
||||||
|
render: (value) => compile(value),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'componentName',
|
||||||
|
title: t('Block type'),
|
||||||
|
render: (value) => value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'actions',
|
||||||
|
title: t('Actions'),
|
||||||
|
render: (_, record) => <Link to={`/admin/block-templates/${record.key}`}>查看</Link>,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
loading={loading}
|
||||||
|
dataSource={data?.data}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,149 @@
|
|||||||
|
import { ISchema, useFieldSchema } from '@formily/react';
|
||||||
|
import { uid } from '@formily/shared';
|
||||||
|
import { Spin } from 'antd';
|
||||||
|
import { cloneDeep } from 'lodash';
|
||||||
|
import React, { createContext, useContext, useMemo } from 'react';
|
||||||
|
import { useAPIClient, useRequest } from '../api-client';
|
||||||
|
import { RouteSwitchContext } from '../route-switch';
|
||||||
|
import { SchemaComponentOptions } from '../schema-component';
|
||||||
|
import { BlockTemplate } from './BlockTemplate';
|
||||||
|
|
||||||
|
export const SchemaTemplateManagerContext = createContext<any>({});
|
||||||
|
|
||||||
|
const SchemaTemplateRouteProvider = (props) => {
|
||||||
|
const { routes, ...others } = useContext(RouteSwitchContext);
|
||||||
|
routes[1].routes.unshift(
|
||||||
|
{
|
||||||
|
type: 'route',
|
||||||
|
path: '/admin/block-templates/:key',
|
||||||
|
component: 'BlockTemplateDetails',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'route',
|
||||||
|
path: '/admin/block-templates',
|
||||||
|
component: 'BlockTemplatePage',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return <RouteSwitchContext.Provider value={{ ...others, routes }}>{props.children}</RouteSwitchContext.Provider>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SchemaTemplateManagerProvider: React.FC<any> = (props) => {
|
||||||
|
const { templates, refresh } = props;
|
||||||
|
return (
|
||||||
|
<SchemaTemplateManagerContext.Provider value={{ templates, refresh }}>
|
||||||
|
<SchemaTemplateRouteProvider>
|
||||||
|
<SchemaComponentOptions components={{ BlockTemplate }}>{props.children}</SchemaComponentOptions>
|
||||||
|
</SchemaTemplateRouteProvider>
|
||||||
|
</SchemaTemplateManagerContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const regenerateUid = (s: ISchema) => {
|
||||||
|
s['x-uid'] = uid();
|
||||||
|
Object.keys(s.properties || {}).forEach((key) => {
|
||||||
|
regenerateUid(s.properties[key]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSchemaTemplate = () => {
|
||||||
|
const { getTemplateBySchemaId } = useSchemaTemplateManager();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const schemaId = fieldSchema['x-uid'];
|
||||||
|
return useMemo(() => getTemplateBySchemaId(schemaId), [schemaId]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSchemaTemplateManager = () => {
|
||||||
|
const { refresh, templates = [] } = useContext(SchemaTemplateManagerContext);
|
||||||
|
const api = useAPIClient();
|
||||||
|
return {
|
||||||
|
templates,
|
||||||
|
refresh,
|
||||||
|
async getTemplateSchemaByMode(options) {
|
||||||
|
const { mode, template } = options;
|
||||||
|
if (mode === 'copy') {
|
||||||
|
const { data } = await api.request({
|
||||||
|
url: `/uiSchemas:getJsonSchema/${template.uid}`,
|
||||||
|
});
|
||||||
|
const s = data?.data || {};
|
||||||
|
regenerateUid(s);
|
||||||
|
return cloneDeep(s);
|
||||||
|
} else if (mode === 'reference') {
|
||||||
|
return {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'BlockTemplate',
|
||||||
|
'x-component-props': {
|
||||||
|
templateId: template.key,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async copyTemplateSchema(template) {
|
||||||
|
const { data } = await api.request({
|
||||||
|
url: `/uiSchemas:getJsonSchema/${template.uid}`,
|
||||||
|
});
|
||||||
|
const s = data?.data || {};
|
||||||
|
regenerateUid(s);
|
||||||
|
return cloneDeep(s);
|
||||||
|
},
|
||||||
|
async saveAsTemplate(values) {
|
||||||
|
const { uid: schemaId } = values;
|
||||||
|
const key = uid();
|
||||||
|
await api.resource('uiSchemaTemplates').create({
|
||||||
|
values: {
|
||||||
|
key,
|
||||||
|
...values,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await api.request({
|
||||||
|
url: `/uiSchemas:clearAncestor/${schemaId}`,
|
||||||
|
});
|
||||||
|
await refresh();
|
||||||
|
return { key };
|
||||||
|
},
|
||||||
|
async duplicate(template) {
|
||||||
|
const { data } = await api.request({
|
||||||
|
url: `/uiSchemas:getJsonSchema/${template.uid}`,
|
||||||
|
});
|
||||||
|
const s = data?.data || {};
|
||||||
|
regenerateUid(s);
|
||||||
|
return s;
|
||||||
|
},
|
||||||
|
getTemplateBySchemaId(schemaId) {
|
||||||
|
return templates?.find((template) => template.uid === schemaId);
|
||||||
|
},
|
||||||
|
getTemplateById(key) {
|
||||||
|
return templates?.find((template) => template.key === key);
|
||||||
|
},
|
||||||
|
getTemplatesByCollection(collectionName: string) {
|
||||||
|
const items = templates?.filter?.((template) => template.collectionName === collectionName);
|
||||||
|
return items || [];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RemoteSchemaTemplateManagerProvider: React.FC = (props) => {
|
||||||
|
const api = useAPIClient();
|
||||||
|
const options = {
|
||||||
|
resource: 'uiSchemaTemplates',
|
||||||
|
action: 'list',
|
||||||
|
params: {
|
||||||
|
appends: ['collection'],
|
||||||
|
paginate: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const service = useRequest(options);
|
||||||
|
if (service.loading) {
|
||||||
|
return <Spin />;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<SchemaTemplateManagerProvider
|
||||||
|
refresh={async () => {
|
||||||
|
const { data } = await api.request(options);
|
||||||
|
service.mutate(data);
|
||||||
|
}}
|
||||||
|
templates={service?.data?.data}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</SchemaTemplateManagerProvider>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,19 @@
|
|||||||
|
import { LayoutOutlined } from '@ant-design/icons';
|
||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import { PluginManager } from '../plugin-manager';
|
||||||
|
|
||||||
|
export const SchemaTemplateShortcut = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const history = useHistory();
|
||||||
|
return (
|
||||||
|
<PluginManager.Toolbar.Item
|
||||||
|
icon={<LayoutOutlined />}
|
||||||
|
title={t('Block templates')}
|
||||||
|
onClick={() => {
|
||||||
|
history.push('/admin/block-templates');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
5
packages/client/src/schema-templates/index.ts
Normal file
5
packages/client/src/schema-templates/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export * from './BlockTemplateDetails';
|
||||||
|
export * from './BlockTemplatePage';
|
||||||
|
export * from './SchemaTemplateManagerProvider';
|
||||||
|
export * from './SchemaTemplateShortcut';
|
||||||
|
|
@ -0,0 +1,153 @@
|
|||||||
|
import { ISchema } from '@formily/react';
|
||||||
|
import { CollectionOptions } from '../../collection-manager';
|
||||||
|
|
||||||
|
const collection: CollectionOptions = {
|
||||||
|
name: 'uiSchemaTemplates',
|
||||||
|
filterTargetKey: 'name',
|
||||||
|
targetKey: 'name',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'integer',
|
||||||
|
name: 'name',
|
||||||
|
interface: 'input',
|
||||||
|
uiSchema: {
|
||||||
|
title: '{{ t("Template name") }}',
|
||||||
|
type: 'number',
|
||||||
|
'x-component': 'Input',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const uiSchemaTemplatesSchema: ISchema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
block1: {
|
||||||
|
type: 'void',
|
||||||
|
'x-collection': 'collections',
|
||||||
|
'x-decorator': 'ResourceActionProvider',
|
||||||
|
'x-decorator-props': {
|
||||||
|
collection,
|
||||||
|
request: {
|
||||||
|
resource: 'uiSchemaTemplates',
|
||||||
|
action: 'list',
|
||||||
|
params: {
|
||||||
|
pageSize: 50,
|
||||||
|
filter: {},
|
||||||
|
// sort: ['sort'],
|
||||||
|
appends: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 'x-component': 'CollectionProvider',
|
||||||
|
// 'x-component-props': {
|
||||||
|
// collection,
|
||||||
|
// },
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ActionBar',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
delete: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Delete") }}',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
useAction: '{{ cm.useBulkDestroyActionAndRefreshCM }}',
|
||||||
|
confirm: {
|
||||||
|
title: "{{t('Delete record')}}",
|
||||||
|
content: "{{t('Are you sure you want to delete it?')}}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Add block template") }}',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'primary',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
type: 'void',
|
||||||
|
'x-uid': 'input',
|
||||||
|
'x-component': 'Table.Void',
|
||||||
|
'x-component-props': {
|
||||||
|
rowKey: 'name',
|
||||||
|
rowSelection: {
|
||||||
|
type: 'checkbox',
|
||||||
|
},
|
||||||
|
useDataSource: '{{ cm.useDataSourceFromRAC }}',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
column1: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'Table.Column.Decorator',
|
||||||
|
'x-component': 'Table.Column',
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
'x-component': 'CollectionField',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
column2: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'Table.Column.Decorator',
|
||||||
|
'x-component': 'Table.Column',
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'CollectionField',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
column3: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Actions") }}',
|
||||||
|
'x-component': 'Table.Column',
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Space',
|
||||||
|
'x-component-props': {
|
||||||
|
split: '|',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
view: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Configure fields") }}',
|
||||||
|
'x-component': 'Action.Link',
|
||||||
|
'x-component-props': {},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Delete") }}',
|
||||||
|
'x-component': 'Action.Link',
|
||||||
|
'x-component-props': {
|
||||||
|
confirm: {
|
||||||
|
title: "{{t('Delete record')}}",
|
||||||
|
content: "{{t('Are you sure you want to delete it?')}}",
|
||||||
|
},
|
||||||
|
useAction: '{{ cm.useDestroyActionAndRefreshCM }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
@ -40,6 +40,16 @@ export class UiRoutesStoragePlugin extends Plugin {
|
|||||||
path: '/admin/workflow',
|
path: '/admin/workflow',
|
||||||
component: 'WorkflowPage',
|
component: 'WorkflowPage',
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// type: 'route',
|
||||||
|
// path: '/admin/block-templates/:key',
|
||||||
|
// component: 'BlockTemplateDetails',
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// type: 'route',
|
||||||
|
// path: '/admin/block-templates',
|
||||||
|
// component: 'BlockTemplatePage',
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
type: 'route',
|
type: 'route',
|
||||||
path: '/admin/:name(.+)?',
|
path: '/admin/:name(.+)?',
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
import { defineCollection } from '@nocobase/database';
|
||||||
|
|
||||||
|
export default defineCollection({
|
||||||
|
name: 'uiSchemaTemplates',
|
||||||
|
autoGenId: false,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'uid',
|
||||||
|
name: 'key',
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'componentName',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsTo',
|
||||||
|
name: 'uiSchema',
|
||||||
|
target: 'uiSchemas',
|
||||||
|
foreignKey: 'uid',
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsTo',
|
||||||
|
name: 'collection',
|
||||||
|
target: 'collections',
|
||||||
|
foreignKey: 'collectionName',
|
||||||
|
targetKey: 'name',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user