diff --git a/packages/core/client/src/acl/ACLProvider.tsx b/packages/core/client/src/acl/ACLProvider.tsx index c73db8595..94442234b 100644 --- a/packages/core/client/src/acl/ACLProvider.tsx +++ b/packages/core/client/src/acl/ACLProvider.tsx @@ -137,7 +137,7 @@ const getIgnoreScope = (options: any = {}) => { const useAllowedActions = () => { const result = useBlockRequestContext() || { service: useResourceActionContext() }; - return result?.service?.data?.meta?.allowedActions; + return result?.allowedActions ?? result?.service?.data?.meta?.allowedActions; }; const useResourceName = () => { diff --git a/packages/core/client/src/block-provider/BlockProvider.tsx b/packages/core/client/src/block-provider/BlockProvider.tsx index 8f76da2c6..223513eaa 100644 --- a/packages/core/client/src/block-provider/BlockProvider.tsx +++ b/packages/core/client/src/block-provider/BlockProvider.tsx @@ -2,9 +2,10 @@ import { css } from '@emotion/css'; import { Field } from '@formily/core'; import { RecursionField, useField, useFieldSchema } from '@formily/react'; import { useRequest } from 'ahooks'; +import merge from 'deepmerge'; import { Col, Row } from 'antd'; import template from 'lodash/template'; -import React, { createContext, useContext } from 'react'; +import React, { createContext, useContext, useEffect, useRef, useState } from 'react'; import { Link } from 'react-router-dom'; import { ACLCollectionProvider, @@ -19,6 +20,7 @@ import { CollectionProvider, useCollection, useCollectionManager } from '../coll import { FilterBlockRecord } from '../filter-provider/FilterProvider'; import { useRecordIndex } from '../record-provider'; import { SharedFilterProvider } from './SharedFilterProvider'; +import _ from 'lodash'; export const BlockResourceContext = createContext(null); export const BlockAssociationContext = createContext(null); @@ -92,17 +94,19 @@ export const useResourceAction = (props, opts = {}) => { /** * fieldName: 来自 TableFieldProvider */ - const { resource, action, fieldName: tableFieldName } = props; + const { resource, action, fieldName: tableFieldName, runWhenParamsChanged = false } = props; const { fields } = useCollection(); - const appends = fields?.filter((field) => field.target).map((field) => field.name); const params = useActionParams(props); const api = useAPIClient(); const fieldSchema = useFieldSchema(); const { snapshot } = useActionContext(); const record = useRecord(); - if (!Object.keys(params).includes('appends') && appends?.length) { - params['appends'] = appends; + if (!Reflect.has(params, 'appends')) { + const appends = fields?.filter((field) => field.target).map((field) => field.name); + if (appends?.length) { + params['appends'] = appends; + } } const result = useRequest( snapshot @@ -113,10 +117,7 @@ export const useResourceAction = (props, opts = {}) => { if (!action) { return Promise.resolve({}); } - const actionParams = { ...opts }; - if (params.appends) { - actionParams.appends = params.appends; - } + const actionParams = { ...params, ...opts }; return resource[action](actionParams).then((res) => res.data); }, { @@ -128,9 +129,22 @@ export const useResourceAction = (props, opts = {}) => { } }, defaultParams: [params], - refreshDeps: [JSON.stringify(params.appends)], + refreshDeps: [runWhenParamsChanged ? JSON.stringify(params.appends) : null], }, ); + + // automatic run service when params has changed + const firstRun = useRef(false); + useEffect(() => { + if (!runWhenParamsChanged) { + return; + } + if (firstRun.current) { + result?.run({ ...result?.params?.[0], ...params }); + } + firstRun.current = true; + }, [JSON.stringify(params), runWhenParamsChanged]); + return result; }; @@ -148,15 +162,29 @@ export const MaybeCollectionProvider = (props) => { const BlockRequestProvider = (props) => { const field = useField(); const resource = useBlockResource(); + const [allowedActions, setAllowedActions] = useState({}); + const service = useResourceAction( { ...props, resource }, { ...props.requestOptions, }, ); + + // Infinite scroll support + const serviceAllowedActions = (service?.data as any)?.meta?.allowedActions; + useEffect(() => { + if (!serviceAllowedActions) return; + setAllowedActions((last) => { + return merge(last, serviceAllowedActions ?? {}); + }); + }, [serviceAllowedActions]); + const __parent = useContext(BlockRequestContext); return ( - + {props.children} ); diff --git a/packages/core/client/src/block-provider/hooks/index.ts b/packages/core/client/src/block-provider/hooks/index.ts index d5698cd8e..83e791eda 100644 --- a/packages/core/client/src/block-provider/hooks/index.ts +++ b/packages/core/client/src/block-provider/hooks/index.ts @@ -1091,7 +1091,7 @@ export const useAssociationNames = (collection) => { const fieldSchema = useFieldSchema(); const associationValues = []; const formSchema = fieldSchema.reduceProperties((buf, schema) => { - if (['FormV2', 'Details'].includes(schema['x-component'])) { + if (['FormV2', 'Details', 'List', 'GridCard'].includes(schema['x-component'])) { return schema; } return buf; diff --git a/packages/core/client/src/collection-manager/CollectionField.tsx b/packages/core/client/src/collection-manager/CollectionField.tsx index fe38e601b..ec20925b5 100644 --- a/packages/core/client/src/collection-manager/CollectionField.tsx +++ b/packages/core/client/src/collection-manager/CollectionField.tsx @@ -69,7 +69,7 @@ const InternalField: React.FC = (props: Props) => { if (!uiSchema) { return null; } - return React.createElement(Component, props, props.children); + return ; }; export const InternalFallbackField = () => { diff --git a/packages/core/client/src/index.tsx b/packages/core/client/src/index.tsx index 3a92d2c1b..0f19eac59 100644 --- a/packages/core/client/src/index.tsx +++ b/packages/core/client/src/index.tsx @@ -24,6 +24,7 @@ export * from './schema-component'; export * from './schema-initializer'; export * from './schema-settings'; export * from './schema-templates'; +export * from './schema-items'; export * from './settings-form'; export * from './system-settings'; export * from './user'; diff --git a/packages/core/client/src/locale/en_US.ts b/packages/core/client/src/locale/en_US.ts index 304d51901..3deb9fb97 100644 --- a/packages/core/client/src/locale/en_US.ts +++ b/packages/core/client/src/locale/en_US.ts @@ -104,6 +104,17 @@ export default { "Table": "Table", "Table OID(Inheritance)":"Table OID(Inheritance)", "Form": "Form", + "List": "List", + "Grid Card": "Grid Card", + "pixels": "pixels", + "Screen size": "Screen size", + "Display title": "Display title", + 'Set the count of columns displayed in a row': "Set the count of columns displayed in a row", + 'Column': 'Column', + 'Phone device': 'Phone device', + 'Tablet device': 'Tablet device', + 'Desktop device': 'Desktop device', + 'Large screen device': 'Large screen device', "Collapse": "Collapse", "Select data source": "Select data source", "Calendar": "Calendar", diff --git a/packages/core/client/src/locale/zh_CN.ts b/packages/core/client/src/locale/zh_CN.ts index 8a606af13..8394b7858 100644 --- a/packages/core/client/src/locale/zh_CN.ts +++ b/packages/core/client/src/locale/zh_CN.ts @@ -120,6 +120,17 @@ export default { "Filter blocks": "筛选区块", "Table": "表格", "Form": "表单", + "List": "列表", + "Grid Card": "网格卡片", + "Screen size": "屏幕尺寸", + "pixels": "像素", + "Display title": "显示标题", + 'Set the count of columns displayed in a row': "设置一行展示的列数", + 'Column': '列', + 'Phone device': '手机设备', + 'Tablet device': '平板设备', + 'Desktop device': '电脑设备', + 'Large screen device': '大屏幕设备', "Table OID(Inheritance)":"数据表 OID(继承)", "Collapse": "折叠面板", "Select data source": "选择数据源", @@ -217,7 +228,6 @@ export default { "Edit collection": "编辑数据表", "Configure fields": "配置字段", "Configure columns": "配置字段", - "Please select the records you want to delete": "请选择要删除的记录", "Edit field": "编辑字段", "Override": "重写", "Override field": "重写字段", diff --git a/packages/core/client/src/record-provider/index.tsx b/packages/core/client/src/record-provider/index.tsx index 5be3ee5f5..fd3cbf85b 100644 --- a/packages/core/client/src/record-provider/index.tsx +++ b/packages/core/client/src/record-provider/index.tsx @@ -12,6 +12,10 @@ export const RecordProvider: React.FC<{ record: any; parent?: any }> = (props) = return {children}; }; +export const RecordSimpleProvider: React.FC<{ value: Record; children: React.ReactNode }> = (props) => { + return ; +}; + export const RecordIndexProvider: React.FC<{ index: any }> = (props) => { const { index, children } = props; return {children}; diff --git a/packages/core/client/src/schema-component/antd/action/ActionBar.tsx b/packages/core/client/src/schema-component/antd/action/ActionBar.tsx index 6b2a1d2da..16dd8cd6b 100644 --- a/packages/core/client/src/schema-component/antd/action/ActionBar.tsx +++ b/packages/core/client/src/schema-component/antd/action/ActionBar.tsx @@ -1,30 +1,34 @@ -import { css } from '@emotion/css'; +import { css, cx } from '@emotion/css'; import { observer, RecursionField, useFieldSchema } from '@formily/react'; import { Space } from 'antd'; import React from 'react'; import { useSchemaInitializer } from '../../../schema-initializer'; import { DndContext } from '../../common'; -import { useDesignable } from '../../hooks'; +import { useDesignable, useProps } from '../../hooks'; export const ActionBar = observer((props: any) => { - const { layout = 'tow-columns', style, ...others } = props; + const { layout = 'tow-columns', style, spaceProps, ...others } = useProps(props); const fieldSchema = useFieldSchema(); - const { render } = useSchemaInitializer(fieldSchema['x-initializer']); + const { InitializerComponent } = useSchemaInitializer(fieldSchema['x-initializer']); const { designable } = useDesignable(); if (layout === 'one-column') { return ( -
+
{props.children && (
- + {fieldSchema.mapProperties((schema, key) => { return ; })}
)} - {render()} +
); @@ -45,6 +49,7 @@ export const ActionBar = observer((props: any) => { } } {...others} + className={cx(others.className, 'nb-action-bar')} >
{ style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%' }} > - + {fieldSchema.mapProperties((schema, key) => { if (schema['x-align'] !== 'left') { return null; @@ -63,7 +68,7 @@ export const ActionBar = observer((props: any) => { return ; })} - + {fieldSchema.mapProperties((schema, key) => { if (schema['x-align'] === 'left') { return null; @@ -73,7 +78,7 @@ export const ActionBar = observer((props: any) => {
- {render()} +
); }); diff --git a/packages/core/client/src/schema-component/antd/association-select/AssociationSelect.tsx b/packages/core/client/src/schema-component/antd/association-select/AssociationSelect.tsx index 4e0fd63fb..c09c34965 100644 --- a/packages/core/client/src/schema-component/antd/association-select/AssociationSelect.tsx +++ b/packages/core/client/src/schema-component/antd/association-select/AssociationSelect.tsx @@ -23,6 +23,7 @@ import { defaultFieldNames } from '../select'; import { FilterDynamicComponent } from '../table-v2/FilterDynamicComponent'; import { ReadPretty } from './ReadPretty'; import useServiceOptions from './useServiceOptions'; +import { GeneralSchemaItems } from '../../../schema-items'; export type AssociationSelectProps

= RemoteSelectProps

& { action?: string; @@ -161,124 +162,7 @@ AssociationSelect.Designer = function Designer() { return ( - {collectionField && ( - { - if (title) { - field.title = title; - fieldSchema.title = title; - dn.emit('patch', { - schema: { - 'x-uid': fieldSchema['x-uid'], - title: fieldSchema.title, - }, - }); - } - dn.refresh(); - }} - /> - )} - {!field.readPretty && ( - { - field.description = description; - fieldSchema.description = description; - dn.emit('patch', { - schema: { - 'x-uid': fieldSchema['x-uid'], - description: fieldSchema.description, - }, - }); - dn.refresh(); - }} - /> - )} - {field.readPretty && ( - { - field.decoratorProps.tooltip = tooltip; - fieldSchema['x-decorator-props'] = fieldSchema['x-decorator-props'] || {}; - fieldSchema['x-decorator-props']['tooltip'] = tooltip; - dn.emit('patch', { - schema: { - 'x-uid': fieldSchema['x-uid'], - 'x-decorator-props': fieldSchema['x-decorator-props'], - }, - }); - dn.refresh(); - }} - /> - )} - {!field.readPretty && ( - { - const schema = { - ['x-uid']: fieldSchema['x-uid'], - }; - field.required = required; - fieldSchema['required'] = required; - schema['required'] = required; - dn.emit('patch', { - schema, - }); - refresh(); - }} - /> - )} + {form && !form?.readPretty && validateSchema && ( - {collectionField && ( - { - if (title) { - field.title = title; - fieldSchema.title = title; - dn.emit('patch', { - schema: { - 'x-uid': fieldSchema['x-uid'], - title: fieldSchema.title, - }, - }); - } - dn.refresh(); - }} - /> - )} - {!field.readPretty && ( - { - field.description = description; - fieldSchema.description = description; - dn.emit('patch', { - schema: { - 'x-uid': fieldSchema['x-uid'], - description: fieldSchema.description, - }, - }); - dn.refresh(); - }} - /> - )} - {field.readPretty && ( - { - field.decoratorProps.tooltip = tooltip; - fieldSchema['x-decorator-props'] = fieldSchema['x-decorator-props'] || {}; - fieldSchema['x-decorator-props']['tooltip'] = tooltip; - dn.emit('patch', { - schema: { - 'x-uid': fieldSchema['x-uid'], - 'x-decorator-props': fieldSchema['x-decorator-props'], - }, - }); - dn.refresh(); - }} - /> - )} + {form && !form?.readPretty && validateSchema && ( { } } }, []); + const { showTitle = true } = props; return ( - {collectionField && ( - { - if (title) { - field.title = title; - fieldSchema.title = title; - dn.emit('patch', { - schema: { - 'x-uid': fieldSchema['x-uid'], - title: fieldSchema.title, - }, - }); - } - dn.refresh(); - }} - /> - )} - {!field.readPretty && ( - { - field.description = description; - fieldSchema.description = description; - dn.emit('patch', { - schema: { - 'x-uid': fieldSchema['x-uid'], - description: fieldSchema.description, - }, - }); - dn.refresh(); - }} - /> - )} - {field.readPretty && ( - { - field.decoratorProps.tooltip = tooltip; - fieldSchema['x-decorator-props'] = fieldSchema['x-decorator-props'] || {}; - fieldSchema['x-decorator-props']['tooltip'] = tooltip; - dn.emit('patch', { - schema: { - 'x-uid': fieldSchema['x-uid'], - 'x-decorator-props': fieldSchema['x-decorator-props'], - }, - }); - dn.refresh(); - }} - /> - )} - {!field.readPretty && fieldSchema['x-component'] !== 'FormField' && ( - { - const schema = { - ['x-uid']: fieldSchema['x-uid'], - }; - field.required = required; - fieldSchema['required'] = required; - schema['required'] = required; - dn.emit('patch', { - schema, - }); - refresh(); - }} - /> - )} + {!form?.readPretty && isFileField ? ( ({}); + +const InternalGridCardBlockProvider = (props) => { + const { resource, service } = useBlockRequestContext(); + const field = useField(); + const form = useMemo(() => { + return createForm({ + readPretty: true, + }); + }, []); + + useEffect(() => { + if (!service?.loading) { + form.setValuesIn(field.address.concat('list').toString(), service?.data?.data); + } + }, [service?.data?.data, service?.loading]); + + return ( + + + +

.nb-block-item { + margin-bottom: var(--nb-spacing); + & > .nb-action-bar:has(:first-child:not(:empty)) { + padding: var(--nb-spacing); + background: #fff; + } + .ant-list-pagination { + padding: var(--nb-spacing); + background: #fff; + } + } + `} + > + {props.children} +
+ + + + ); +}; + +export const GridCardBlockProvider = (props) => { + const params = { ...props.params }; + const { collection } = props; + const { appends } = useAssociationNames(collection); + if (!Object.keys(params).includes('appends')) { + params['appends'] = appends; + } + + return ( + + + + ); +}; + +export const useGridCardBlockContext = () => { + return useContext(GridCardBlockContext); +}; + +export const useGridCardItemProps = () => { + return {}; +}; diff --git a/packages/core/client/src/schema-component/antd/grid-card/GridCard.Designer.tsx b/packages/core/client/src/schema-component/antd/grid-card/GridCard.Designer.tsx new file mode 100644 index 000000000..7a0ff54a0 --- /dev/null +++ b/packages/core/client/src/schema-component/antd/grid-card/GridCard.Designer.tsx @@ -0,0 +1,245 @@ +import { useFieldSchema, useField, ISchema } from '@formily/react'; +import React, { useMemo } from 'react'; +import { ArrayItems } from '@formily/antd'; +import { Slider } from 'antd'; +import _ from 'lodash'; +import { useTranslation } from 'react-i18next'; +import { useCollection, useCollectionFilterOptions, useSortFields } from '../../../collection-manager'; +import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings'; +import { useSchemaTemplate } from '../../../schema-templates'; +import { useDesignable } from '../../hooks'; +import { removeNullCondition } from '../filter'; +import { FilterDynamicComponent } from '../table-v2/FilterDynamicComponent'; +import { SchemaComponentOptions } from '../../core'; +import { defaultColumnCount, gridSizes, pageSizeOptions, screenSizeMaps, screenSizeTitleMaps } from './options'; +Slider; + +const columnCountMarks = [1, 2, 3, 4, 6, 8, 12, 24].reduce((obj, cur) => { + obj[cur] = cur; + return obj; +}, {}); + +export const GridCardDesigner = () => { + const { name, title } = useCollection(); + const template = useSchemaTemplate(); + const { t } = useTranslation(); + const fieldSchema = useFieldSchema(); + const field = useField(); + const dataSource = useCollectionFilterOptions(name); + const { dn } = useDesignable(); + const sortFields = useSortFields(name); + const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {}; + const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || []; + const defaultResource = fieldSchema?.['x-decorator-props']?.resource; + const columnCount = field.decoratorProps.columnCount || defaultColumnCount; + + const columnCountSchema = useMemo(() => { + return { + 'x-component': 'Slider', + 'x-decorator': 'FormItem', + 'x-component-props': { + min: 1, + max: 24, + marks: columnCountMarks, + tooltip: { + formatter: (value) => `${value}${t('Column')}`, + }, + step: null, + }, + }; + }, [t]); + + const columnCountProperties = useMemo(() => { + return gridSizes.reduce((o, k) => { + o[k] = { + ...columnCountSchema, + title: t(screenSizeTitleMaps[k]), + description: `${t('Screen size')} ${screenSizeMaps[k]} ${t('pixels')}`, + }; + return o; + }, {}); + }, [columnCountSchema, t]); + + const sort = defaultSort?.map((item: string) => { + return item.startsWith('-') + ? { + field: item.substring(1), + direction: 'desc', + } + : { + field: item, + direction: 'asc', + }; + }); + return ( + + + + { + _.set(fieldSchema, 'x-decorator-props.columnCount', columnCount); + field.decoratorProps.columnCount = columnCount; + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-decorator-props': fieldSchema['x-decorator-props'], + }, + }); + }} + /> + FilterDynamicComponent({ ...props }), + }, + }, + }, + } as ISchema + } + onSubmit={({ filter }) => { + filter = removeNullCondition(filter); + _.set(fieldSchema, 'x-decorator-props.params.filter', filter); + field.decoratorProps.params = { ...fieldSchema['x-decorator-props'].params }; + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-decorator-props': fieldSchema['x-decorator-props'], + }, + }); + }} + /> + { + const sortArr = sort.map((item) => { + return item.direction === 'desc' ? `-${item.field}` : item.field; + }); + + _.set(fieldSchema, 'x-decorator-props.params.sort', sortArr); + field.decoratorProps.params = { ...fieldSchema['x-decorator-props'].params }; + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-decorator-props': fieldSchema['x-decorator-props'], + }, + }); + }} + /> + ({ value: v }))} + onChange={(pageSize) => { + _.set(fieldSchema, 'x-decorator-props.params.pageSize', pageSize); + field.decoratorProps.params = { ...fieldSchema['x-decorator-props'].params, page: 1 }; + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-decorator-props': fieldSchema['x-decorator-props'], + }, + }); + }} + /> + + + + + + ); +}; diff --git a/packages/core/client/src/schema-component/antd/grid-card/GridCard.Item.tsx b/packages/core/client/src/schema-component/antd/grid-card/GridCard.Item.tsx new file mode 100644 index 000000000..3d91e983b --- /dev/null +++ b/packages/core/client/src/schema-component/antd/grid-card/GridCard.Item.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { Card } from 'antd'; +import { css } from '@emotion/css'; +import { useField } from '@formily/react'; +import { ObjectField } from '@formily/core'; +import { RecordSimpleProvider } from '../../../record-provider'; + +const itemCss = css` + display: flex; + width: 100%; + height: 100%; + flex-direction: column; + justify-content: space-between; + gap: 8px; +`; + +export const GridCardItem = (props) => { + const field = useField(); + return ( + +
+ {props.children} +
+
+ ); +}; diff --git a/packages/core/client/src/schema-component/antd/grid-card/GridCard.tsx b/packages/core/client/src/schema-component/antd/grid-card/GridCard.tsx new file mode 100644 index 000000000..1f50982a3 --- /dev/null +++ b/packages/core/client/src/schema-component/antd/grid-card/GridCard.tsx @@ -0,0 +1,150 @@ +import React, { useCallback, useState } from 'react'; +import { RecursionField, Schema, useField, useFieldSchema } from '@formily/react'; +import { css, cx } from '@emotion/css'; +import { List as AntdList, PaginationProps, Col } from 'antd'; +import { useGridCardActionBarProps } from './hooks'; +import { SortableItem } from '../../common'; +import { SchemaComponentOptions } from '../../core'; +import { useDesigner } from '../../hooks'; +import { GridCardItem } from './GridCard.Item'; +import { useGridCardBlockContext, useGridCardItemProps, GridCardBlockProvider } from './GridCard.Decorator'; +import { GridCardDesigner } from './GridCard.Designer'; +import { ArrayField } from '@formily/core'; +import { defaultColumnCount, pageSizeOptions } from './options'; + +const rowGutter = { + md: 16, + sm: 8, + xs: 8, +}; + +const designerCss = css` + width: 100%; + &:hover { + > .general-schema-designer { + display: block; + } + } + + > .general-schema-designer { + position: absolute; + z-index: 999; + top: 0; + bottom: 0; + left: 0; + right: 0; + display: none; + background: rgba(241, 139, 98, 0.06); + border: 0; + pointer-events: none; + > .general-schema-designer-icons { + position: absolute; + right: 2px; + top: 2px; + line-height: 16px; + pointer-events: all; + .ant-space-item { + background-color: #f18b62; + color: #fff; + line-height: 16px; + width: 16px; + padding-left: 1px; + } + } + } +`; + +const InternalGridCard = (props) => { + const { service, columnCount = defaultColumnCount } = useGridCardBlockContext(); + const { run, params } = service; + const meta = service?.data?.meta; + const fieldSchema = useFieldSchema(); + const field = useField(); + const Designer = useDesigner(); + const [schemaMap] = useState(new Map()); + const getSchema = useCallback( + (key) => { + if (!schemaMap.has(key)) { + schemaMap.set( + key, + new Schema({ + type: 'object', + properties: { + [key]: fieldSchema.properties['item'], + }, + }), + ); + } + return schemaMap.get(key); + }, + [fieldSchema.properties, schemaMap], + ); + + const onPaginationChange: PaginationProps['onChange'] = useCallback( + (page, pageSize) => { + run({ + ...params?.[0], + page: page, + pageSize: pageSize, + }); + }, + [run, params], + ); + + return ( + + + { + return ( + + + + ); + }} + loading={service?.loading} + /> + + + + ); +}; + +export const GridCard = InternalGridCard as typeof InternalGridCard & { + Item: typeof GridCardItem; + Designer: typeof GridCardDesigner; + Decorator: typeof GridCardBlockProvider; +}; + +GridCard.Item = GridCardItem; +GridCard.Designer = GridCardDesigner; +GridCard.Decorator = GridCardBlockProvider; diff --git a/packages/core/client/src/schema-component/antd/grid-card/hooks.ts b/packages/core/client/src/schema-component/antd/grid-card/hooks.ts new file mode 100644 index 000000000..d4d00ebb1 --- /dev/null +++ b/packages/core/client/src/schema-component/antd/grid-card/hooks.ts @@ -0,0 +1,12 @@ +import { SpaceProps } from 'antd'; + +const spaceProps: SpaceProps = { + size: ['large', 'small'], + wrap: true, +}; + +export const useGridCardActionBarProps = () => { + return { + spaceProps, + }; +}; diff --git a/packages/core/client/src/schema-component/antd/grid-card/index.ts b/packages/core/client/src/schema-component/antd/grid-card/index.ts new file mode 100644 index 000000000..46fa672a0 --- /dev/null +++ b/packages/core/client/src/schema-component/antd/grid-card/index.ts @@ -0,0 +1 @@ +export * from './GridCard'; diff --git a/packages/core/client/src/schema-component/antd/grid-card/options.ts b/packages/core/client/src/schema-component/antd/grid-card/options.ts new file mode 100644 index 000000000..8df7b54df --- /dev/null +++ b/packages/core/client/src/schema-component/antd/grid-card/options.ts @@ -0,0 +1,21 @@ +export const pageSizeOptions = [12, 24, 60, 96, 120]; +export const gridSizes = ['xs', 'md', 'lg', 'xxl']; +export const screenSizeTitleMaps = { + xs: 'Phone device', + md: 'Tablet device', + lg: 'Desktop device', + xxl: 'Large screen device', +}; +export const screenSizeMaps = { + xs: '< 768', + md: '≥ 768', + lg: '≥ 992', + xxl: '≥ 1600', +}; + +export const defaultColumnCount = { + xs: 1, + md: 2, + lg: 3, + xxl: 4, +}; diff --git a/packages/core/client/src/schema-component/antd/grid/Grid.tsx b/packages/core/client/src/schema-component/antd/grid/Grid.tsx index af2afd3e1..049b96ccc 100644 --- a/packages/core/client/src/schema-component/antd/grid/Grid.tsx +++ b/packages/core/client/src/schema-component/antd/grid/Grid.tsx @@ -3,7 +3,7 @@ import { css } from '@emotion/css'; import { observer, RecursionField, Schema, useField, useFieldSchema } from '@formily/react'; import { uid } from '@formily/shared'; import cls from 'classnames'; -import React, { createContext, useContext, useEffect, useRef, useState } from 'react'; +import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react'; import { useDesignable, useFormBlockContext, useSchemaInitializer } from '../../../'; import { DndContext } from '../../common/dnd-context'; @@ -293,22 +293,26 @@ const wrapColSchema = (schema: Schema) => { const useRowProperties = () => { const fieldSchema = useFieldSchema(); - return fieldSchema.reduceProperties((buf, s) => { - if (s['x-component'] === 'Grid.Row' && !s['x-hidden']) { - buf.push(s); - } - return buf; - }, []); + return useMemo(() => { + return fieldSchema.reduceProperties((buf, s) => { + if (s['x-component'] === 'Grid.Row' && !s['x-hidden']) { + buf.push(s); + } + return buf; + }, []); + }, [Object.keys(fieldSchema.properties || {}).join(',')]); }; const useColProperties = () => { const fieldSchema = useFieldSchema(); - return fieldSchema.reduceProperties((buf, s) => { - if (s['x-component'] === 'Grid.Col' && !s['x-hidden']) { - buf.push(s); - } - return buf; - }, []); + return useMemo(() => { + return fieldSchema.reduceProperties((buf, s) => { + if (s['x-component'] === 'Grid.Col' && !s['x-hidden']) { + buf.push(s); + } + return buf; + }, []); + }, [Object.keys(fieldSchema.properties || {}).join(',')]); }; const DndWrapper = (props) => { @@ -330,7 +334,7 @@ export const Grid: any = observer((props: any) => { const gridRef = useRef(null); const field = useField(); const fieldSchema = useFieldSchema(); - const { render } = useSchemaInitializer(fieldSchema['x-initializer']); + const { render, InitializerComponent } = useSchemaInitializer(fieldSchema['x-initializer']); const addr = field.address.toString(); const rows = useRowProperties(); const { setPrintContent } = useFormBlockContext(); @@ -338,8 +342,9 @@ export const Grid: any = observer((props: any) => { useEffect(() => { gridRef.current && setPrintContent?.(gridRef.current); }, [gridRef.current]); + return ( - +
{ /> {rows.map((schema, index) => { return ( - + { ); })} - {render()} +
); @@ -411,7 +416,7 @@ Grid.Row = observer(() => { /> {cols.map((schema, index) => { return ( - + { const { cols = [] } = useContext(GridRowContext); const schema = useFieldSchema(); const field = useField(); - let width = ''; - if (cols?.length) { - const w = schema?.['x-component-props']?.['width'] || 100 / cols.length; - width = `calc(${w}% - var(--nb-spacing) * ${(cols.length + 1) / cols.length})`; - } + + const width = useMemo(() => { + let width = ''; + if (cols?.length) { + const w = schema?.['x-component-props']?.['width'] || 100 / cols.length; + width = `calc(${w}% - var(--nb-spacing) * ${(cols.length + 1) / cols.length})`; + } + return width; + }, [cols?.length, schema?.['x-component-props']?.['width']]); + const { setNodeRef } = useDroppable({ id: field.address.toString(), data: { diff --git a/packages/core/client/src/schema-component/antd/index.ts b/packages/core/client/src/schema-component/antd/index.ts index 646e66d1d..25424b435 100644 --- a/packages/core/client/src/schema-component/antd/index.ts +++ b/packages/core/client/src/schema-component/antd/index.ts @@ -19,10 +19,12 @@ export * from './form-v2'; export * from './g2plot'; export * from './gantt'; export * from './grid'; +export * from './grid-card'; export * from './icon-picker'; export * from './input'; export * from './input-number'; export * from './kanban'; +export * from './list'; export * from './markdown'; export * from './menu'; export * from './page'; diff --git a/packages/core/client/src/schema-component/antd/list/List.Decorator.tsx b/packages/core/client/src/schema-component/antd/list/List.Decorator.tsx new file mode 100644 index 000000000..fafe6c315 --- /dev/null +++ b/packages/core/client/src/schema-component/antd/list/List.Decorator.tsx @@ -0,0 +1,61 @@ +import { createForm } from '@formily/core'; +import { FormContext, useField } from '@formily/react'; +import React, { createContext, useContext, useEffect, useMemo } from 'react'; +import { FormLayout } from '@formily/antd'; +import { useAssociationNames } from '../../../block-provider/hooks'; +import { BlockProvider, useBlockRequestContext } from '../../../block-provider'; + +export const ListBlockContext = createContext({}); + +const InternalListBlockProvider = (props) => { + const { resource, service } = useBlockRequestContext(); + + const field = useField(); + const form = useMemo(() => { + return createForm({ + readPretty: true, + }); + }, []); + + useEffect(() => { + if (!service?.loading) { + form.setValuesIn(field.address.concat('list').toString(), service?.data?.data); + } + }, [service?.data?.data, service?.loading]); + + return ( + + + {props.children} + + + ); +}; + +export const ListBlockProvider = (props) => { + const params = { ...props.params }; + const { collection } = props; + const { appends } = useAssociationNames(collection); + if (!Object.keys(params).includes('appends')) { + params['appends'] = appends; + } + + return ( + + + + ); +}; + +export const useListBlockContext = () => { + return useContext(ListBlockContext); +}; + +export const useListItemProps = () => { + return {}; +}; diff --git a/packages/core/client/src/schema-component/antd/list/List.Designer.tsx b/packages/core/client/src/schema-component/antd/list/List.Designer.tsx new file mode 100644 index 000000000..2f58e8951 --- /dev/null +++ b/packages/core/client/src/schema-component/antd/list/List.Designer.tsx @@ -0,0 +1,192 @@ +import { useFieldSchema, useField, ISchema } from '@formily/react'; +import React from 'react'; +import { ArrayItems } from '@formily/antd'; +import { useListBlockContext } from './List.Decorator'; +import _ from 'lodash'; +import { useTranslation } from 'react-i18next'; +import { useCollection, useCollectionFilterOptions, useSortFields } from '../../../collection-manager'; +import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings'; +import { useSchemaTemplate } from '../../../schema-templates'; +import { useDesignable } from '../../hooks'; +import { removeNullCondition } from '../filter'; +import { FilterDynamicComponent } from '../table-v2/FilterDynamicComponent'; + +export const ListDesigner = () => { + const { name, title } = useCollection(); + const template = useSchemaTemplate(); + const { t } = useTranslation(); + const fieldSchema = useFieldSchema(); + const field = useField(); + const dataSource = useCollectionFilterOptions(name); + const { service } = useListBlockContext(); + const { dn } = useDesignable(); + const sortFields = useSortFields(name); + const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {}; + const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || []; + const defaultResource = fieldSchema?.['x-decorator-props']?.resource; + const sort = defaultSort?.map((item: string) => { + return item.startsWith('-') + ? { + field: item.substring(1), + direction: 'desc', + } + : { + field: item, + direction: 'asc', + }; + }); + return ( + + + FilterDynamicComponent({ ...props }), + }, + }, + }, + } as ISchema + } + onSubmit={({ filter }) => { + filter = removeNullCondition(filter); + _.set(fieldSchema, 'x-decorator-props.params.filter', filter); + field.decoratorProps.params = { ...fieldSchema['x-decorator-props'].params, page: 1 }; + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-decorator-props': fieldSchema['x-decorator-props'], + }, + }); + }} + /> + { + const sortArr = sort.map((item) => { + return item.direction === 'desc' ? `-${item.field}` : item.field; + }); + + _.set(fieldSchema, 'x-decorator-props.params.sort', sortArr); + field.decoratorProps.params = { ...fieldSchema['x-decorator-props'].params, page: 1 }; + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-decorator-props': fieldSchema['x-decorator-props'], + }, + }); + }} + /> + { + _.set(fieldSchema, 'x-decorator-props.params.pageSize', pageSize); + field.decoratorProps.params = { ...fieldSchema['x-decorator-props'].params, page: 1 }; + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-decorator-props': fieldSchema['x-decorator-props'], + }, + }); + }} + /> + + + + + ); +}; diff --git a/packages/core/client/src/schema-component/antd/list/List.Item.tsx b/packages/core/client/src/schema-component/antd/list/List.Item.tsx new file mode 100644 index 000000000..c206b3a05 --- /dev/null +++ b/packages/core/client/src/schema-component/antd/list/List.Item.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { css } from '@emotion/css'; +import { useField } from '@formily/react'; +import { ObjectField } from '@formily/core'; +import { RecordSimpleProvider } from '../../../record-provider'; + +const itemCss = css` + display: flex; + width: 100%; + flex-direction: column; + gap: 8px; + padding: 12px 0; + border-bottom: 1px solid #f0f0f0; +`; + +export const ListItem = (props) => { + const field = useField(); + return ( +
+ {props.children} +
+ ); +}; diff --git a/packages/core/client/src/schema-component/antd/list/List.tsx b/packages/core/client/src/schema-component/antd/list/List.tsx new file mode 100644 index 000000000..bc4757c25 --- /dev/null +++ b/packages/core/client/src/schema-component/antd/list/List.tsx @@ -0,0 +1,135 @@ +import { ListDesigner } from './List.Designer'; +import { ListBlockProvider, useListBlockContext, useListItemProps } from './List.Decorator'; +import React, { useCallback, useEffect, useState } from 'react'; +import { RecursionField, Schema, useField, useFieldSchema } from '@formily/react'; +import { css, cx } from '@emotion/css'; +import { List as AntdList, PaginationProps } from 'antd'; +import { useListActionBarProps } from './hooks'; +import { SortableItem } from '../../common'; +import { SchemaComponentOptions } from '../../core'; +import { useDesigner } from '../../hooks'; +import { ListItem } from './List.Item'; +import { ArrayField } from '@formily/core'; + +const designerCss = css` + width: 100%; + margin-bottom: var(--nb-spacing); + &:hover { + > .general-schema-designer { + display: block; + } + } + + > .general-schema-designer { + position: absolute; + z-index: 999; + top: 0; + bottom: 0; + left: 0; + right: 0; + display: none; + background: rgba(241, 139, 98, 0.06); + border: 0; + pointer-events: none; + > .general-schema-designer-icons { + position: absolute; + right: 2px; + top: 2px; + line-height: 16px; + pointer-events: all; + .ant-space-item { + background-color: #f18b62; + color: #fff; + line-height: 16px; + width: 16px; + padding-left: 1px; + } + } + } +`; + +const InternalList = (props) => { + const { service } = useListBlockContext(); + const { run, params } = service; + const fieldSchema = useFieldSchema(); + const Designer = useDesigner(); + const meta = service?.data?.meta; + const field = useField(); + const [schemaMap] = useState(new Map()); + const getSchema = useCallback( + (key) => { + if (!schemaMap.has(key)) { + schemaMap.set( + key, + new Schema({ + type: 'object', + properties: { + [key]: fieldSchema.properties['item'], + }, + }), + ); + } + return schemaMap.get(key); + }, + [fieldSchema.properties, schemaMap], + ); + + const onPaginationChange: PaginationProps['onChange'] = useCallback( + (page, pageSize) => { + run({ + ...params?.[0], + page: page, + pageSize: pageSize, + }); + }, + [run, params], + ); + + return ( + + + + {field.value?.map((item, index) => { + return ( + + ); + })} + + + + + ); +}; + +export const List = InternalList as typeof InternalList & { + Item: typeof ListItem; + Designer: typeof ListDesigner; + Decorator: typeof ListBlockProvider; +}; + +List.Item = ListItem; +List.Designer = ListDesigner; +List.Decorator = ListBlockProvider; diff --git a/packages/core/client/src/schema-component/antd/list/hooks.ts b/packages/core/client/src/schema-component/antd/list/hooks.ts new file mode 100644 index 000000000..e687eb122 --- /dev/null +++ b/packages/core/client/src/schema-component/antd/list/hooks.ts @@ -0,0 +1,12 @@ +import { SpaceProps } from 'antd'; + +const spaceProps: SpaceProps = { + size: ['large', 'small'], + wrap: true, +}; + +export const useListActionBarProps = () => { + return { + spaceProps, + }; +}; diff --git a/packages/core/client/src/schema-component/antd/list/index.ts b/packages/core/client/src/schema-component/antd/list/index.ts new file mode 100644 index 000000000..4994c1813 --- /dev/null +++ b/packages/core/client/src/schema-component/antd/list/index.ts @@ -0,0 +1 @@ +export * from './List'; diff --git a/packages/core/client/src/schema-initializer/SchemaInitializerProvider.tsx b/packages/core/client/src/schema-initializer/SchemaInitializerProvider.tsx index 02c63f6d5..25f54f908 100644 --- a/packages/core/client/src/schema-initializer/SchemaInitializerProvider.tsx +++ b/packages/core/client/src/schema-initializer/SchemaInitializerProvider.tsx @@ -1,7 +1,7 @@ import { useFieldSchema } from '@formily/react'; import { isPlainObj } from '@formily/shared'; import get from 'lodash/get'; -import React, { createContext, useContext } from 'react'; +import React, { createContext, useContext, useEffect, useMemo, useRef } from 'react'; import { SchemaComponentOptions } from '../schema-component'; import { SchemaInitializer } from './SchemaInitializer'; import * as globals from './buttons'; @@ -22,20 +22,32 @@ export const useSchemaInitializer = (name: string, props = {}) => { return component && React.createElement(component, props); }; - if (!name) { - return { exists: false, render: (props?: any) => render(null) }; - } - - const initializer = get(initializers, name || fieldSchema?.['x-initializer']); + const initializerPropsRef = useRef({}); + const initializer = name ? get(initializers, name || fieldSchema?.['x-initializer']) : null; const initializerProps = { ...props, ...fieldSchema?.['x-initializer-props'] }; + initializerPropsRef.current = initializerProps; + + const InitializerComponent = useMemo(() => { + let C = React.Fragment; + if (!initializer) { + return C; + } else if (isPlainObj(initializer)) { + C = (initializer as any).component || SchemaInitializer.Button; + return (p) => ; + } else { + C = initializer; + return (p) => ; + } + }, [initializer]); if (!initializer) { - return { exists: false, render: (props?: any) => render(null) }; + return { exists: false, InitializerComponent, render: (props?: any) => render(null) }; } if (isPlainObj(initializer)) { return { exists: true, + InitializerComponent, render: (props?: any) => { const component = (initializer as any).component || SchemaInitializer.Button; return render(component, { ...initializer, ...initializerProps, ...props }); @@ -45,6 +57,7 @@ export const useSchemaInitializer = (name: string, props = {}) => { return { exists: true, + InitializerComponent, render: (props?: any) => render(initializer, { ...initializerProps, ...props }), }; }; diff --git a/packages/core/client/src/schema-initializer/buttons/BlockInitializers.tsx b/packages/core/client/src/schema-initializer/buttons/BlockInitializers.tsx index 142959fb9..531e0f43a 100644 --- a/packages/core/client/src/schema-initializer/buttons/BlockInitializers.tsx +++ b/packages/core/client/src/schema-initializer/buttons/BlockInitializers.tsx @@ -18,6 +18,18 @@ export const BlockInitializers = { title: '{{t("Table")}}', component: 'TableBlockInitializer', }, + { + key: 'List', + type: 'item', + title: '{{t("List")}}', + component: 'ListBlockInitializer', + }, + { + key: 'GridCard', + type: 'item', + title: '{{t("Grid Card")}}', + component: 'GridCardBlockInitializer', + }, { key: 'form', type: 'item', diff --git a/packages/core/client/src/schema-initializer/buttons/GridCardActionInitializers.tsx b/packages/core/client/src/schema-initializer/buttons/GridCardActionInitializers.tsx new file mode 100644 index 000000000..5cf9289e2 --- /dev/null +++ b/packages/core/client/src/schema-initializer/buttons/GridCardActionInitializers.tsx @@ -0,0 +1,299 @@ +import { useFieldSchema, Schema } from '@formily/react'; +import { useCollection } from '../../collection-manager'; + +// 表单的操作配置 +export const GridCardActionInitializers = { + title: "{{t('Configure actions')}}", + icon: 'SettingOutlined', + style: { + marginLeft: 8, + }, + items: [ + { + type: 'itemGroup', + title: "{{t('Enable actions')}}", + children: [ + { + type: 'item', + title: "{{t('Filter')}}", + component: 'FilterActionInitializer', + schema: { + 'x-align': 'left', + }, + }, + { + type: 'item', + title: "{{t('Add new')}}", + component: 'CreateActionInitializer', + schema: { + 'x-align': 'right', + 'x-decorator': 'ACLActionProvider', + 'x-acl-action-props': { + skipScopeCheck: true, + }, + }, + visible: () => { + const collection = useCollection(); + return collection.template !== 'view' && collection.template !== 'file'; + }, + }, + { + type: 'item', + title: "{{t('Refresh')}}", + component: 'RefreshActionInitializer', + schema: { + 'x-align': 'right', + }, + }, + { + type: 'item', + title: "{{t('Import')}}", + component: 'ImportActionInitializer', + schema: { + 'x-align': 'right', + 'x-decorator': 'ACLActionProvider', + 'x-acl-action-props': { + skipScopeCheck: true, + }, + }, + }, + { + type: 'item', + title: "{{t('Export')}}", + component: 'ExportActionInitializer', + schema: { + 'x-align': 'right', + 'x-decorator': 'ACLActionProvider', + 'x-acl-action-props': { + skipScopeCheck: true, + }, + }, + }, + ], + }, + // { + // type: 'divider', + // visible: () => { + // const collection = useCollection(); + // return (collection as any).template !== 'view'; + // }, + // }, + // { + // type: 'subMenu', + // title: '{{t("Customize")}}', + // children: [ + // { + // type: 'item', + // title: '{{t("Bulk update")}}', + // component: 'CustomizeActionInitializer', + // schema: { + // type: 'void', + // title: '{{ t("Bulk update") }}', + // 'x-component': 'Action', + // 'x-align': 'right', + // 'x-acl-action': 'update', + // 'x-decorator': 'ACLActionProvider', + // 'x-acl-action-props': { + // skipScopeCheck: true, + // }, + // 'x-action': 'customize:bulkUpdate', + // 'x-designer': 'Action.Designer', + // 'x-action-settings': { + // assignedValues: {}, + // updateMode: 'selected', + // onSuccess: { + // manualClose: true, + // redirecting: false, + // successMessage: '{{t("Updated successfully")}}', + // }, + // }, + // 'x-component-props': { + // icon: 'EditOutlined', + // useProps: '{{ useCustomizeBulkUpdateActionProps }}', + // }, + // }, + // }, + // { + // type: 'item', + // title: '{{t("Bulk edit")}}', + // component: 'CustomizeBulkEditActionInitializer', + // schema: { + // 'x-align': 'right', + // 'x-decorator': 'ACLActionProvider', + // 'x-acl-action': 'update', + // 'x-acl-action-props': { + // skipScopeCheck: true, + // }, + // }, + // }, + // ], + // visible: () => { + // const collection = useCollection(); + // return (collection as any).template !== 'view'; + // }, + // }, + ], +}; + +export const GridCardItemActionInitializers = { + title: '{{t("Configure actions")}}', + icon: 'SettingOutlined', + items: [ + { + type: 'itemGroup', + title: '{{t("Enable actions")}}', + children: [ + { + type: 'item', + title: '{{t("View")}}', + component: 'ViewActionInitializer', + schema: { + 'x-component': 'Action.Link', + 'x-action': 'view', + 'x-decorator': 'ACLActionProvider', + 'x-align': 'left', + }, + }, + { + type: 'item', + title: '{{t("Edit")}}', + component: 'UpdateActionInitializer', + schema: { + 'x-component': 'Action.Link', + 'x-action': 'update', + 'x-decorator': 'ACLActionProvider', + 'x-align': 'left', + }, + visible: () => { + const collection = useCollection(); + return (collection as any).template !== 'view'; + }, + }, + { + type: 'item', + title: '{{t("Delete")}}', + component: 'DestroyActionInitializer', + schema: { + 'x-component': 'Action.Link', + 'x-action': 'destroy', + 'x-decorator': 'ACLActionProvider', + 'x-align': 'left', + }, + }, + ], + }, + { + type: 'divider', + }, + { + type: 'subMenu', + title: '{{t("Customize")}}', + children: [ + { + type: 'item', + title: '{{t("Popup")}}', + component: 'CustomizeActionInitializer', + schema: { + type: 'void', + title: '{{ t("Popup") }}', + 'x-action': 'customize:popup', + 'x-designer': 'Action.Designer', + 'x-component': 'Action.Link', + 'x-component-props': { + openMode: 'drawer', + }, + properties: { + drawer: { + type: 'void', + title: '{{ t("Popup") }}', + 'x-component': 'Action.Container', + 'x-component-props': { + className: 'nb-action-popup', + }, + properties: { + tabs: { + type: 'void', + 'x-component': 'Tabs', + 'x-component-props': {}, + 'x-initializer': 'TabPaneInitializers', + properties: { + tab1: { + type: 'void', + title: '{{t("Details")}}', + 'x-component': 'Tabs.TabPane', + 'x-designer': 'Tabs.Designer', + 'x-component-props': {}, + properties: { + grid: { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'RecordBlockInitializers', + properties: {}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + type: 'item', + title: '{{t("Update record")}}', + component: 'CustomizeActionInitializer', + schema: { + title: '{{ t("Update record") }}', + 'x-component': 'Action.Link', + 'x-action': 'customize:update', + 'x-decorator': 'ACLActionProvider', + 'x-acl-action': 'update', + 'x-designer': 'Action.Designer', + 'x-action-settings': { + assignedValues: {}, + onSuccess: { + manualClose: true, + redirecting: false, + successMessage: '{{t("Updated successfully")}}', + }, + }, + 'x-component-props': { + useProps: '{{ useCustomizeUpdateActionProps }}', + }, + }, + visible: () => { + const collection = useCollection(); + return (collection as any).template !== 'view'; + }, + }, + { + type: 'item', + title: '{{t("Custom request")}}', + component: 'CustomizeActionInitializer', + schema: { + title: '{{ t("Custom request") }}', + 'x-component': 'Action.Link', + 'x-action': 'customize:table:request', + 'x-designer': 'Action.Designer', + 'x-action-settings': { + requestSettings: {}, + onSuccess: { + manualClose: false, + redirecting: false, + successMessage: '{{t("Request success")}}', + }, + }, + 'x-component-props': { + useProps: '{{ useCustomizeRequestActionProps }}', + }, + }, + visible: () => { + const collection = useCollection(); + return (collection as any).template !== 'view'; + }, + }, + ], + }, + ], +}; diff --git a/packages/core/client/src/schema-initializer/buttons/ListActionInitializers.tsx b/packages/core/client/src/schema-initializer/buttons/ListActionInitializers.tsx new file mode 100644 index 000000000..5f6affded --- /dev/null +++ b/packages/core/client/src/schema-initializer/buttons/ListActionInitializers.tsx @@ -0,0 +1,299 @@ +import { useFieldSchema, Schema } from '@formily/react'; +import { useCollection } from '../../collection-manager'; + +// 表单的操作配置 +export const ListActionInitializers = { + title: "{{t('Configure actions')}}", + icon: 'SettingOutlined', + style: { + marginLeft: 8, + }, + items: [ + { + type: 'itemGroup', + title: "{{t('Enable actions')}}", + children: [ + { + type: 'item', + title: "{{t('Filter')}}", + component: 'FilterActionInitializer', + schema: { + 'x-align': 'left', + }, + }, + { + type: 'item', + title: "{{t('Add new')}}", + component: 'CreateActionInitializer', + schema: { + 'x-align': 'right', + 'x-decorator': 'ACLActionProvider', + 'x-acl-action-props': { + skipScopeCheck: true, + }, + }, + visible: () => { + const collection = useCollection(); + return collection.template !== 'view' && collection.template !== 'file'; + }, + }, + { + type: 'item', + title: "{{t('Refresh')}}", + component: 'RefreshActionInitializer', + schema: { + 'x-align': 'right', + }, + }, + { + type: 'item', + title: "{{t('Import')}}", + component: 'ImportActionInitializer', + schema: { + 'x-align': 'right', + 'x-decorator': 'ACLActionProvider', + 'x-acl-action-props': { + skipScopeCheck: true, + }, + }, + }, + { + type: 'item', + title: "{{t('Export')}}", + component: 'ExportActionInitializer', + schema: { + 'x-align': 'right', + 'x-decorator': 'ACLActionProvider', + 'x-acl-action-props': { + skipScopeCheck: true, + }, + }, + }, + ], + }, + // { + // type: 'divider', + // visible: () => { + // const collection = useCollection(); + // return (collection as any).template !== 'view'; + // }, + // }, + // { + // type: 'subMenu', + // title: '{{t("Customize")}}', + // children: [ + // { + // type: 'item', + // title: '{{t("Bulk update")}}', + // component: 'CustomizeActionInitializer', + // schema: { + // type: 'void', + // title: '{{ t("Bulk update") }}', + // 'x-component': 'Action', + // 'x-align': 'right', + // 'x-acl-action': 'update', + // 'x-decorator': 'ACLActionProvider', + // 'x-acl-action-props': { + // skipScopeCheck: true, + // }, + // 'x-action': 'customize:bulkUpdate', + // 'x-designer': 'Action.Designer', + // 'x-action-settings': { + // assignedValues: {}, + // updateMode: 'selected', + // onSuccess: { + // manualClose: true, + // redirecting: false, + // successMessage: '{{t("Updated successfully")}}', + // }, + // }, + // 'x-component-props': { + // icon: 'EditOutlined', + // useProps: '{{ useCustomizeBulkUpdateActionProps }}', + // }, + // }, + // }, + // { + // type: 'item', + // title: '{{t("Bulk edit")}}', + // component: 'CustomizeBulkEditActionInitializer', + // schema: { + // 'x-align': 'right', + // 'x-decorator': 'ACLActionProvider', + // 'x-acl-action': 'update', + // 'x-acl-action-props': { + // skipScopeCheck: true, + // }, + // }, + // }, + // ], + // visible: () => { + // const collection = useCollection(); + // return (collection as any).template !== 'view'; + // }, + // }, + ], +}; + +export const ListItemActionInitializers = { + title: '{{t("Configure actions")}}', + icon: 'SettingOutlined', + items: [ + { + type: 'itemGroup', + title: '{{t("Enable actions")}}', + children: [ + { + type: 'item', + title: '{{t("View")}}', + component: 'ViewActionInitializer', + schema: { + 'x-component': 'Action.Link', + 'x-action': 'view', + 'x-decorator': 'ACLActionProvider', + 'x-align': 'left', + }, + }, + { + type: 'item', + title: '{{t("Edit")}}', + component: 'UpdateActionInitializer', + schema: { + 'x-component': 'Action.Link', + 'x-action': 'update', + 'x-decorator': 'ACLActionProvider', + 'x-align': 'left', + }, + visible: () => { + const collection = useCollection(); + return (collection as any).template !== 'view'; + }, + }, + { + type: 'item', + title: '{{t("Delete")}}', + component: 'DestroyActionInitializer', + schema: { + 'x-component': 'Action.Link', + 'x-action': 'destroy', + 'x-decorator': 'ACLActionProvider', + 'x-align': 'left', + }, + }, + ], + }, + { + type: 'divider', + }, + { + type: 'subMenu', + title: '{{t("Customize")}}', + children: [ + { + type: 'item', + title: '{{t("Popup")}}', + component: 'CustomizeActionInitializer', + schema: { + type: 'void', + title: '{{ t("Popup") }}', + 'x-action': 'customize:popup', + 'x-designer': 'Action.Designer', + 'x-component': 'Action.Link', + 'x-component-props': { + openMode: 'drawer', + }, + properties: { + drawer: { + type: 'void', + title: '{{ t("Popup") }}', + 'x-component': 'Action.Container', + 'x-component-props': { + className: 'nb-action-popup', + }, + properties: { + tabs: { + type: 'void', + 'x-component': 'Tabs', + 'x-component-props': {}, + 'x-initializer': 'TabPaneInitializers', + properties: { + tab1: { + type: 'void', + title: '{{t("Details")}}', + 'x-component': 'Tabs.TabPane', + 'x-designer': 'Tabs.Designer', + 'x-component-props': {}, + properties: { + grid: { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'RecordBlockInitializers', + properties: {}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + type: 'item', + title: '{{t("Update record")}}', + component: 'CustomizeActionInitializer', + schema: { + title: '{{ t("Update record") }}', + 'x-component': 'Action.Link', + 'x-action': 'customize:update', + 'x-decorator': 'ACLActionProvider', + 'x-acl-action': 'update', + 'x-designer': 'Action.Designer', + 'x-action-settings': { + assignedValues: {}, + onSuccess: { + manualClose: true, + redirecting: false, + successMessage: '{{t("Updated successfully")}}', + }, + }, + 'x-component-props': { + useProps: '{{ useCustomizeUpdateActionProps }}', + }, + }, + visible: () => { + const collection = useCollection(); + return (collection as any).template !== 'view'; + }, + }, + { + type: 'item', + title: '{{t("Custom request")}}', + component: 'CustomizeActionInitializer', + schema: { + title: '{{ t("Custom request") }}', + 'x-component': 'Action.Link', + 'x-action': 'customize:table:request', + 'x-designer': 'Action.Designer', + 'x-action-settings': { + requestSettings: {}, + onSuccess: { + manualClose: false, + redirecting: false, + successMessage: '{{t("Request success")}}', + }, + }, + 'x-component-props': { + useProps: '{{ useCustomizeRequestActionProps }}', + }, + }, + visible: () => { + const collection = useCollection(); + return (collection as any).template !== 'view'; + }, + }, + ], + }, + ], +}; diff --git a/packages/core/client/src/schema-initializer/buttons/index.ts b/packages/core/client/src/schema-initializer/buttons/index.ts index 0b061f3e0..02ec66bc1 100644 --- a/packages/core/client/src/schema-initializer/buttons/index.ts +++ b/packages/core/client/src/schema-initializer/buttons/index.ts @@ -21,5 +21,8 @@ export * from './TableColumnInitializers'; export * from './TableSelectorInitializers'; export * from './TabPaneInitializers'; export * from './GanttActionInitializers'; +export * from './DetailsActionInitializers'; +export * from './ListActionInitializers'; +export * from './GridCardActionInitializers'; // association filter export * from '../../schema-component/antd/association-filter/AssociationFilter'; diff --git a/packages/core/client/src/schema-initializer/items/GridCardBlockInitializer.tsx b/packages/core/client/src/schema-initializer/items/GridCardBlockInitializer.tsx new file mode 100644 index 000000000..f4cfae945 --- /dev/null +++ b/packages/core/client/src/schema-initializer/items/GridCardBlockInitializer.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { OrderedListOutlined } from '@ant-design/icons'; +import { createGridCardBlockSchema, createListBlockSchema } from '../utils'; +import { DataBlockInitializer } from './DataBlockInitializer'; +import { useCollectionManager } from '../../collection-manager'; + +export const GridCardBlockInitializer = (props) => { + const { insert } = props; + const { getCollection } = useCollectionManager(); + return ( + } + componentType={'GridCard'} + onCreateBlockSchema={async ({ item }) => { + const collection = getCollection(item.name); + const schema = createGridCardBlockSchema({ + collection: item.name, + rowKey: collection.filterTargetKey || 'id', + }); + insert(schema); + }} + /> + ); +}; diff --git a/packages/core/client/src/schema-initializer/items/InitializerWithSwitch.tsx b/packages/core/client/src/schema-initializer/items/InitializerWithSwitch.tsx index 275947f4b..97d6bdd96 100644 --- a/packages/core/client/src/schema-initializer/items/InitializerWithSwitch.tsx +++ b/packages/core/client/src/schema-initializer/items/InitializerWithSwitch.tsx @@ -3,6 +3,8 @@ import React from 'react'; import { SchemaInitializer } from '..'; import { useCurrentSchema } from '../utils'; +import { useBlockRequestContext } from '../../block-provider'; +import { useCollection } from '../../collection-manager'; export const InitializerWithSwitch = (props) => { const { type, schema, item, insert, remove: passInRemove } = props; diff --git a/packages/core/client/src/schema-initializer/items/ListBlockInitializer.tsx b/packages/core/client/src/schema-initializer/items/ListBlockInitializer.tsx new file mode 100644 index 000000000..cd499454c --- /dev/null +++ b/packages/core/client/src/schema-initializer/items/ListBlockInitializer.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { OrderedListOutlined } from '@ant-design/icons'; +import { createListBlockSchema } from '../utils'; +import { DataBlockInitializer } from './DataBlockInitializer'; +import { useCollectionManager } from '../../collection-manager'; + +export const ListBlockInitializer = (props) => { + const { insert } = props; + const { getCollection } = useCollectionManager(); + return ( + } + componentType={'List'} + onCreateBlockSchema={async ({ item }) => { + const collection = getCollection(item.name); + const schema = createListBlockSchema({ + collection: item.name, + rowKey: collection.filterTargetKey || 'id', + }); + insert(schema); + }} + /> + ); +}; diff --git a/packages/core/client/src/schema-initializer/items/index.tsx b/packages/core/client/src/schema-initializer/items/index.tsx index dde2b3d1d..5fab23dec 100644 --- a/packages/core/client/src/schema-initializer/items/index.tsx +++ b/packages/core/client/src/schema-initializer/items/index.tsx @@ -29,8 +29,10 @@ export * from './FilterFormBlockInitializer'; export * from './FormBlockInitializer'; export * from './G2PlotInitializer'; export * from './GanttBlockInitializer'; +export * from './GridCardBlockInitializer'; export * from './InitializerWithSwitch'; export * from './KanbanBlockInitializer'; +export * from './ListBlockInitializer'; export * from './MarkdownBlockInitializer'; export * from './PrintActionInitializer'; export * from './RecordAssociationBlockInitializer'; @@ -41,6 +43,7 @@ export * from './RecordFormBlockInitializer'; export * from './RecordReadPrettyAssociationFormBlockInitializer'; export * from './RecordReadPrettyFormBlockInitializer'; export * from './RefreshActionInitializer'; +export * from './SelectActionInitializer'; export * from './SubmitActionInitializer'; export * from './TableActionColumnInitializer'; export * from './TableBlockInitializer'; @@ -49,4 +52,3 @@ export * from './TableSelectorInitializer'; export * from './UpdateActionInitializer'; export * from './UpdateSubmitActionInitializer'; export * from './ViewActionInitializer'; -export * from './SelectActionInitializer'; diff --git a/packages/core/client/src/schema-initializer/utils.ts b/packages/core/client/src/schema-initializer/utils.ts index 0ec4f6e05..dcd9d8f1d 100644 --- a/packages/core/client/src/schema-initializer/utils.ts +++ b/packages/core/client/src/schema-initializer/utils.ts @@ -950,6 +950,177 @@ export const createDetailsBlockSchema = (options) => { return schema; }; +export const createListBlockSchema = (options) => { + const { + formItemInitializers = 'ReadPrettyFormItemInitializers', + actionInitializers = 'ListActionInitializers', + itemActionInitializers = 'ListItemActionInitializers', + collection, + association, + resource, + template, + ...others + } = options; + const resourceName = resource || association || collection; + const schema: ISchema = { + type: 'void', + 'x-acl-action': `${resourceName}:view`, + 'x-decorator': 'List.Decorator', + 'x-decorator-props': { + resource: resourceName, + collection, + association, + readPretty: true, + action: 'list', + params: { + pageSize: 10, + }, + runWhenParamsChanged: true, + ...others, + }, + 'x-component': 'CardItem', + 'x-designer': 'List.Designer', + properties: { + actionBar: { + type: 'void', + 'x-initializer': actionInitializers, + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 'var(--nb-spacing)', + }, + }, + properties: {}, + }, + list: { + type: 'array', + 'x-component': 'List', + 'x-component-props': { + props: '{{ useListBlockProps }}', + }, + properties: { + item: { + type: 'object', + 'x-component': 'List.Item', + 'x-read-pretty': true, + 'x-component-props': { + useProps: '{{ useListItemProps }}', + }, + properties: { + grid: template || { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': formItemInitializers, + 'x-initializer-props': { + useProps: '{{ useListItemInitializerProps }}', + }, + properties: {}, + }, + actionBar: { + type: 'void', + 'x-align': 'left', + 'x-initializer': itemActionInitializers, + 'x-component': 'ActionBar', + 'x-component-props': { + useProps: '{{ useListActionBarProps }}', + layout: 'one-column', + }, + properties: {}, + }, + }, + }, + }, + }, + }, + }; + return schema; +}; + +export const createGridCardBlockSchema = (options) => { + const { + formItemInitializers = 'ReadPrettyFormItemInitializers', + actionInitializers = 'GridCardActionInitializers', + itemActionInitializers = 'GridCardItemActionInitializers', + collection, + association, + resource, + template, + ...others + } = options; + const resourceName = resource || association || collection; + const schema: ISchema = { + type: 'void', + 'x-acl-action': `${resourceName}:view`, + 'x-decorator': 'GridCard.Decorator', + 'x-decorator-props': { + resource: resourceName, + collection, + association, + readPretty: true, + action: 'list', + params: { + pageSize: 12, + }, + runWhenParamsChanged: true, + ...others, + }, + 'x-component': 'BlockItem', + 'x-designer': 'GridCard.Designer', + properties: { + actionBar: { + type: 'void', + 'x-initializer': actionInitializers, + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 'var(--nb-spacing)', + }, + }, + properties: {}, + }, + list: { + type: 'array', + 'x-component': 'GridCard', + 'x-component-props': { + props: '{{ useGridCardBlockProps }}', + }, + properties: { + item: { + type: 'object', + 'x-component': 'GridCard.Item', + 'x-read-pretty': true, + 'x-component-props': { + useProps: '{{ useGridCardItemProps }}', + }, + properties: { + grid: template || { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': formItemInitializers, + 'x-initializer-props': { + useProps: '{{ useGridCardItemInitializerProps }}', + }, + properties: {}, + }, + actionBar: { + type: 'void', + 'x-align': 'left', + 'x-initializer': itemActionInitializers, + 'x-component': 'ActionBar', + 'x-component-props': { + useProps: '{{ useGridCardActionBarProps }}', + layout: 'one-column', + }, + properties: {}, + }, + }, + }, + }, + }, + }, + }; + return schema; +}; export const createFormBlockSchema = (options) => { const { formItemInitializers = 'FormItemInitializers', @@ -1177,7 +1348,7 @@ export const createTableBlockSchema = (options) => { 'x-component': 'ActionBar', 'x-component-props': { style: { - marginBottom: 16, + marginBottom: 'var(--nb-spacing)', }, }, properties: {}, @@ -1276,7 +1447,7 @@ export const createTableSelectorSchema = (options) => { 'x-component': 'ActionBar', 'x-component-props': { style: { - marginBottom: 16, + marginBottom: 'var(--nb-spacing)', }, }, properties: {}, @@ -1546,7 +1717,7 @@ export const createKanbanBlockSchema = (options) => { 'x-component': 'ActionBar', 'x-component-props': { style: { - marginBottom: 16, + marginBottom: 'var(--nb-spacing)', }, }, properties: {}, diff --git a/packages/core/client/src/schema-items/GeneralSchemaItems.tsx b/packages/core/client/src/schema-items/GeneralSchemaItems.tsx new file mode 100644 index 000000000..fb57f4a74 --- /dev/null +++ b/packages/core/client/src/schema-items/GeneralSchemaItems.tsx @@ -0,0 +1,161 @@ +import { Field } from '@formily/core'; +import { ISchema, useField, useFieldSchema } from '@formily/react'; +import _ from 'lodash'; +import { useTranslation } from 'react-i18next'; +import React from 'react'; +import { useCollectionManager, useCollection } from '../collection-manager'; +import { useDesignable } from '../schema-component'; +import { SchemaSettings } from '../schema-settings'; + +export const GeneralSchemaItems: React.FC<{ + required?: boolean; +}> = (props) => { + const { required = true } = props; + const { getCollectionJoinField } = useCollectionManager(); + const { getField } = useCollection(); + const field = useField(); + const fieldSchema = useFieldSchema(); + const { t } = useTranslation(); + const { dn, refresh } = useDesignable(); + const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']); + + return ( + <> + {collectionField && ( + { + if (title) { + field.title = title; + fieldSchema.title = title; + dn.emit('patch', { + schema: { + 'x-uid': fieldSchema['x-uid'], + title: fieldSchema.title, + }, + }); + } + dn.refresh(); + }} + /> + )} + { + field.decoratorProps.showTitle = checked; + dn.emit('patch', { + schema: { + 'x-uid': fieldSchema['x-uid'], + 'x-decorator-props': { + ...fieldSchema['x-decorator-props'], + showTitle: checked, + }, + }, + }); + dn.refresh(); + }} + > + {!field.readPretty && ( + { + field.description = description; + fieldSchema.description = description; + dn.emit('patch', { + schema: { + 'x-uid': fieldSchema['x-uid'], + description: fieldSchema.description, + }, + }); + dn.refresh(); + }} + /> + )} + {field.readPretty && ( + { + field.decoratorProps.tooltip = tooltip; + fieldSchema['x-decorator-props'] = fieldSchema['x-decorator-props'] || {}; + fieldSchema['x-decorator-props']['tooltip'] = tooltip; + dn.emit('patch', { + schema: { + 'x-uid': fieldSchema['x-uid'], + 'x-decorator-props': fieldSchema['x-decorator-props'], + }, + }); + dn.refresh(); + }} + /> + )} + {!field.readPretty && fieldSchema['x-component'] !== 'FormField' && required && ( + { + const schema = { + ['x-uid']: fieldSchema['x-uid'], + }; + field.required = required; + fieldSchema['required'] = required; + schema['required'] = required; + dn.emit('patch', { + schema, + }); + refresh(); + }} + /> + )} + + ); +}; diff --git a/packages/core/client/src/schema-items/index.ts b/packages/core/client/src/schema-items/index.ts index d137ec60c..33e40ea98 100644 --- a/packages/core/client/src/schema-items/index.ts +++ b/packages/core/client/src/schema-items/index.ts @@ -1 +1,2 @@ export * from './OpenModeSchemaItems'; +export * from './GeneralSchemaItems'; diff --git a/packages/core/client/src/schema-settings/GeneralSchemaDesigner.tsx b/packages/core/client/src/schema-settings/GeneralSchemaDesigner.tsx index 5a2e4a458..02ee01c79 100644 --- a/packages/core/client/src/schema-settings/GeneralSchemaDesigner.tsx +++ b/packages/core/client/src/schema-settings/GeneralSchemaDesigner.tsx @@ -3,7 +3,7 @@ import { css } from '@emotion/css'; import { useField, useFieldSchema } from '@formily/react'; import { Space } from 'antd'; import classNames from 'classnames'; -import React from 'react'; +import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { DragHandler, useCompile, useDesignable, useGridContext, useGridRowContext } from '../schema-component'; import { gridRowColWrap } from '../schema-initializer/utils'; @@ -43,14 +43,24 @@ export const GeneralSchemaDesigner = (props: any) => { field, fieldSchema, }; - if (!designable) { - return null; - } + const rowCtx = useGridRowContext(); const ctx = useGridContext(); const templateName = ['FormItem', 'ReadPrettyFormItem'].includes(template?.componentName) ? `${template?.name} ${t('(Fields only)')}` : template?.name; + const initializerProps = useMemo(() => { + return { + insertPosition: 'afterEnd', + wrap: rowCtx?.cols?.length > 1 ? undefined : gridRowColWrap, + component: , + }; + }, [rowCtx?.cols?.length]); + + if (!designable) { + return null; + } + return (
{title && ( @@ -73,11 +83,11 @@ export const GeneralSchemaDesigner = (props: any) => { )} {!disableInitializer && - ctx?.renderSchemaInitializer?.({ - insertPosition: 'afterEnd', - wrap: rowCtx?.cols?.length > 1 ? undefined : gridRowColWrap, - component: , - })} + (ctx?.InitializerComponent ? ( + + ) : ( + ctx?.renderSchemaInitializer?.(initializerProps) + ))} } {...schemaSettingsProps}> {props.children} diff --git a/packages/plugins/map/src/client/components/Designer.tsx b/packages/plugins/map/src/client/components/Designer.tsx index 157ef701b..5288474f8 100644 --- a/packages/plugins/map/src/client/components/Designer.tsx +++ b/packages/plugins/map/src/client/components/Designer.tsx @@ -2,6 +2,7 @@ import { Field } from '@formily/core'; import { ISchema, useField, useFieldSchema } from '@formily/react'; import { GeneralSchemaDesigner, + GeneralSchemaItems, SchemaSettings, isPatternDisabled, useCollection, @@ -40,122 +41,7 @@ const Designer = () => { return ( - { - if (title) { - field.title = title; - fieldSchema.title = title; - dn.emit('patch', { - schema: { - 'x-uid': fieldSchema['x-uid'], - title: fieldSchema.title, - }, - }); - } - dn.refresh(); - }} - /> - {!field.readPretty && ( - { - field.description = description; - fieldSchema.description = description; - dn.emit('patch', { - schema: { - 'x-uid': fieldSchema['x-uid'], - description: fieldSchema.description, - }, - }); - dn.refresh(); - }} - /> - )} - {field.readPretty && ( - { - field.decoratorProps.tooltip = tooltip; - fieldSchema['x-decorator-props'] = fieldSchema['x-decorator-props'] || {}; - fieldSchema['x-decorator-props']['tooltip'] = tooltip; - dn.emit('patch', { - schema: { - 'x-uid': fieldSchema['x-uid'], - 'x-decorator-props': fieldSchema['x-decorator-props'], - }, - }); - dn.refresh(); - }} - /> - )} - {!field.readPretty && ( - { - const schema = { - ['x-uid']: fieldSchema['x-uid'], - }; - field.required = required; - fieldSchema['required'] = required; - schema['required'] = required; - dn.emit('patch', { - schema, - }); - refresh(); - }} - /> - )} + {form && !form?.readPretty && !isPatternDisabled(fieldSchema) && (