feat(association-field): association field support sub-form(popover) (#2373)

* refactor: association field support sub-form(popover)

* refactor: code improve

* refactor: code improve

* refactor: sub-table support field component cinfig

* refactor: sub-table support field component cinfig

* refactor: sub-table support field component cinfig

* refactor: code improve

* refactor: code improve

* refactor: useAssociationNames

* refactor: locale improve

* refactor: locale improve

* refactor: code improve

* refactor: code improve

* refactor: code improve

* fix: code improve

* refactor: code refactor

* style: style improve

* Update database.ts

* refactor: code imporve

* refactor: code imporve

* style: style improve

* style: style improve

* refactor: code imporve

* style: style improve

* refactor: code imporve

---------

Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
katherinehhh 2023-08-04 09:48:03 +08:00 committed by GitHub
parent d84c52618d
commit d333ad201a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 239 additions and 159 deletions

View File

@ -151,12 +151,8 @@ export const useCreateActionProps = () => {
return {
async onClick() {
const fieldNames = fields.map((field) => field.name);
const {
assignedValues: originalAssignedValues = {},
onSuccess,
overwriteValues,
skipValidator,
} = actionSchema?.['x-action-settings'] ?? {};
const { assignedValues: originalAssignedValues = {}, onSuccess, overwriteValues, skipValidator } =
actionSchema?.['x-action-settings'] ?? {};
const addChild = fieldSchema?.['x-component-props']?.addChild;
const assignedValues = parse(originalAssignedValues)({ currentTime: new Date(), currentRecord, currentUser });
if (!skipValidator) {
@ -226,12 +222,8 @@ export const useAssociationCreateActionProps = () => {
return {
async onClick() {
const fieldNames = fields.map((field) => field.name);
const {
assignedValues: originalAssignedValues = {},
onSuccess,
overwriteValues,
skipValidator,
} = actionSchema?.['x-action-settings'] ?? {};
const { assignedValues: originalAssignedValues = {}, onSuccess, overwriteValues, skipValidator } =
actionSchema?.['x-action-settings'] ?? {};
const addChild = fieldSchema?.['x-component-props']?.addChild;
const assignedValues = parse(originalAssignedValues)({ currentTime: new Date(), currentRecord, currentUser });
if (!skipValidator) {
@ -415,11 +407,8 @@ export const useCustomizeUpdateActionProps = () => {
return {
async onClick() {
const {
assignedValues: originalAssignedValues = {},
onSuccess,
skipValidator,
} = actionSchema?.['x-action-settings'] ?? {};
const { assignedValues: originalAssignedValues = {}, onSuccess, skipValidator } =
actionSchema?.['x-action-settings'] ?? {};
const assignedValues = parse(originalAssignedValues)({ currentTime: new Date(), currentRecord, currentUser });
if (skipValidator === false) {
await form.submit();
@ -473,11 +462,8 @@ export const useCustomizeBulkUpdateActionProps = () => {
return {
async onClick() {
const {
assignedValues: originalAssignedValues = {},
onSuccess,
updateMode,
} = actionSchema?.['x-action-settings'] ?? {};
const { assignedValues: originalAssignedValues = {}, onSuccess, updateMode } =
actionSchema?.['x-action-settings'] ?? {};
actionField.data = field.data || {};
actionField.data.loading = true;
const assignedValues = parse(originalAssignedValues)({ currentTime: new Date(), currentUser });
@ -732,12 +718,8 @@ export const useUpdateActionProps = () => {
return {
async onClick() {
const {
assignedValues: originalAssignedValues = {},
onSuccess,
overwriteValues,
skipValidator,
} = actionSchema?.['x-action-settings'] ?? {};
const { assignedValues: originalAssignedValues = {}, onSuccess, overwriteValues, skipValidator } =
actionSchema?.['x-action-settings'] ?? {};
const assignedValues = parse(originalAssignedValues)({ currentTime: new Date(), currentRecord, currentUser });
if (!skipValidator) {
await form.submit();
@ -1124,7 +1106,7 @@ export const useAssociationNames = () => {
const fieldPath = !isAssociationField && isAssociationSubfield ? getAssociationPath(s.name) : s.name;
const path = prefix === '' || !prefix ? fieldPath : prefix + '.' + fieldPath;
appends.add(path);
if (['Nester', 'SubTable'].includes(s['x-component-props']?.mode)) {
if (['Nester', 'SubTable', 'PopoverNester'].includes(s['x-component-props']?.mode)) {
updateAssociationValues.add(path);
const bufPrefix = prefix && prefix !== '' ? prefix + '.' + s.name : s.name;
getAssociationAppends(s, bufPrefix);

View File

@ -251,6 +251,7 @@ export default {
"Link to description": "Used to create collection relationships quickly and compatible with most common scenarios. Suitable for non-developer use. When present as a field, it is a drop-down selection used to select records from the target collection. Once created, it will simultaneously generate the associated fields of the current collection in the target collection.",
"Sub-table": "Sub-table",
"Sub-details":"Sub-details",
"Sub-form(Popover)":"Sub-form(Popover)",
"System info": "System info",
"Created at": "Created at",
"Last updated at": "Last updated at",

View File

@ -222,6 +222,7 @@ export default {
"Link to": "リンク",
"Link to description": "コレクションの関連付けを素早く作成するためにしようされ、ほとんどの一般的なシナリオに対応しています。開発者以外の方のしようにも適しています。フィールドとして存在する場合、参照元コレクションのレコードを選択するために使用されるドロップダウンです。一度作成されると、参照先コレクションに現在のコレクションの関連フィールドが同時に生成されます。",
"Sub-table": "サブテーブル",
"Sub-form(Popover)":"サブフォーム(ポップアップ窓)",
"System info": "システム情報",
"Created at": "作成日",
"Last updated at": "最終更新日",

View File

@ -317,6 +317,7 @@ export default {
"Select file": "选择文件",
"Subtable": "子表格",
"Sub-form": "子表单",
"Sub-form(Popover)":"子表单(弹窗)",
"Sub-details":"子详情",
"Record picker": "数据选择器",
"Toggles the subfield mode": "切换子字段模式",

View File

@ -28,19 +28,6 @@ export const AssociationFieldProvider = observer(
[fieldSchema['x-component-props']?.mode],
);
// const targetKeyValue = useMemo(() => {
// if (!field.value) return '';
// if (['belongsTo', 'hasOne'].includes(collectionField.type)) {
// return field.value[collectionField.targetKey] ?? '';
// }
// if (['belongsToMany', 'hasMany'].includes(collectionField.type)) {
// if (Array.isArray(field.value)) {
// return field.value.map((v) => v[collectionField.targetKey] ?? '').join(',');
// }
// }
// return '';
// }, [collectionField, field.value]);
const fieldValue = useMemo(() => JSON.stringify(field.value), [field.value]);
const [loading, setLoading] = useState(true);
@ -52,7 +39,7 @@ export const AssociationFieldProvider = observer(
return;
}
// 如果是表单模板数据,使用子表单和子表格组件时,过滤掉关系 ID
if (field.value && field.form['__template'] && ['Nester', 'SubTable'].includes(currentMode)) {
if (field.value && field.form['__template'] && ['Nester', 'SubTable', 'PopoverNester'].includes(currentMode)) {
if (['belongsTo', 'hasOne'].includes(collectionField.type)) {
if (field.value?.[collectionField.targetKey]) {
delete field.value[collectionField.targetKey];
@ -69,7 +56,7 @@ export const AssociationFieldProvider = observer(
}
if (field.value !== null && field.value !== undefined) {
// Nester 子表单时,如果没数据初始化一个 [null] 的占位
if (currentMode === 'Nester' && Array.isArray(field.value)) {
if (['Nester', 'PopoverNester'].includes(currentMode) && Array.isArray(field.value)) {
if (field.value.length === 0 && ['belongsToMany', 'hasMany'].includes(collectionField.type)) {
field.value = [{}];
}
@ -77,7 +64,7 @@ export const AssociationFieldProvider = observer(
setLoading(false);
return;
}
if (currentMode === 'Nester') {
if (['Nester', 'PopoverNester'].includes(currentMode)) {
if (['belongsTo', 'hasOne'].includes(collectionField.type)) {
field.value = {};
} else if (['belongsToMany', 'hasMany'].includes(collectionField.type)) {

View File

@ -9,6 +9,7 @@ import { InternalFileManager } from './FileManager';
import { InternalNester } from './InternalNester';
import { InternalPicker } from './InternalPicker';
import { InternalSubTable } from './InternalSubTable';
import { InternaPopoverNester } from './InternalPopoverNester';
import { CreateRecordAction } from './components/CreateRecordAction';
import { useAssociationFieldContext } from './hooks';
@ -45,6 +46,7 @@ const EditableAssociationField = observer(
<SchemaComponentOptions scope={{ useCreateActionProps }} components={{ CreateRecordAction }}>
{currentMode === 'Picker' && <InternalPicker {...props} />}
{currentMode === 'Nester' && <InternalNester {...props} />}
{currentMode === 'PopoverNester' && <InternaPopoverNester {...props} />}
{currentMode === 'Select' && <AssociationSelect {...props} />}
{currentMode === 'SubTable' && <InternalSubTable {...props} />}
{currentMode === 'FileManager' && <InternalFileManager {...props} />}

View File

@ -23,13 +23,9 @@ import { flatData, getLabelFormatValue, isShowFilePicker, useLabelUiSchema } fro
const useTableSelectorProps = () => {
const field: any = useField();
const {
multiple,
options = [],
setSelectedRows,
selectedRows: rcSelectRows = [],
onChange,
} = useContext(RecordPickerContext);
const { multiple, options = [], setSelectedRows, selectedRows: rcSelectRows = [], onChange } = useContext(
RecordPickerContext,
);
const { onRowSelectionChange, rowKey = 'id', ...others } = useTsp();
const { setVisible } = useActionContext();
return {
@ -72,6 +68,7 @@ const InternalFileManager = (props) => {
const collectionField = getField(field.props.name);
const labelUiSchema = useLabelUiSchema(collectionField?.target, fieldNames?.label || 'label');
const compile = useCompile();
const { setVisible, modalProps } = useActionContext();
const getFilter = () => {
const targetKey = collectionField?.targetKey || 'id';
const list = options.map((option) => option[targetKey]).filter(Boolean);
@ -119,7 +116,6 @@ const InternalFileManager = (props) => {
collectionField,
};
const usePickActionProps = () => {
const { setVisible } = useActionContext();
const { multiple, selectedRows, onChange, options, collectionField } = useContext(RecordPickerContext);
return {
onClick() {
@ -159,7 +155,7 @@ const InternalFileManager = (props) => {
visible: visibleSelector,
setVisible: setVisibleSelector,
modalProps: {
getContainer: others?.getContainer,
getContainer: others?.getContainer || modalProps?.getContainer,
},
formValueChanged: false,
}}

View File

@ -1,58 +1,61 @@
import { css, cx } from '@emotion/css';
import { FormLayout } from '@formily/antd-v5';
import { RecursionField, useField, useFieldSchema } from '@formily/react';
import { RecursionField, useField, useFieldSchema, observer } from '@formily/react';
import React, { useEffect } from 'react';
import { CollectionProvider } from '../../../collection-manager';
import { useAssociationFieldContext, useInsertSchema } from './hooks';
import schema from './schema';
export const InternalNester = () => {
const field = useField();
const fieldSchema = useFieldSchema();
const insertNester = useInsertSchema('Nester');
const { options: collectionField } = useAssociationFieldContext();
const showTitle = fieldSchema['x-decorator-props']?.showTitle ?? true;
useEffect(() => {
insertNester(schema.Nester);
}, []);
return (
<CollectionProvider name={collectionField.target}>
<FormLayout layout={'vertical'}>
<div
className={cx(
css`
& .ant-formily-item-layout-vertical {
margin-bottom: 10px;
}
.ant-card-body {
padding: 15px 20px 5px;
}
.ant-divider-horizontal {
margin: 10px 0;
}
`,
{
[css`
export const InternalNester = observer(
() => {
const field = useField();
const fieldSchema = useFieldSchema();
const insertNester = useInsertSchema('Nester');
const { options: collectionField } = useAssociationFieldContext();
const showTitle = fieldSchema['x-decorator-props']?.showTitle ?? true;
useEffect(() => {
insertNester(schema.Nester);
}, []);
return (
<CollectionProvider name={collectionField.target}>
<FormLayout layout={'vertical'}>
<div
className={cx(
css`
& .ant-formily-item-layout-vertical {
margin-bottom: 10px;
}
.ant-card-body {
padding: 0px 20px 20px 0px;
padding: 15px 20px 5px;
}
> .ant-card-bordered {
border: none;
.ant-divider-horizontal {
margin: 10px 0;
}
`]: showTitle === false,
},
)}
>
<RecursionField
onlyRenderProperties
basePath={field.address}
schema={fieldSchema}
filterProperties={(s) => {
return s['x-component'] === 'AssociationField.Nester';
}}
/>
</div>
</FormLayout>
</CollectionProvider>
);
};
`,
{
[css`
.ant-card-body {
padding: 0px 20px 20px 0px;
}
> .ant-card-bordered {
border: none;
}
`]: showTitle === false,
},
)}
>
<RecursionField
onlyRenderProperties
basePath={field.address}
schema={fieldSchema}
filterProperties={(s) => {
return s['x-component'] === 'AssociationField.Nester';
}}
/>
</div>
</FormLayout>
</CollectionProvider>
);
},
{ displayName: 'InternalNester' },
);

View File

@ -23,13 +23,9 @@ import { flatData, getLabelFormatValue, useLabelUiSchema } from './util';
const useTableSelectorProps = () => {
const field: any = useField();
const {
multiple,
options = [],
setSelectedRows,
selectedRows: rcSelectRows = [],
onChange,
} = useContext(RecordPickerContext);
const { multiple, options = [], setSelectedRows, selectedRows: rcSelectRows = [], onChange } = useContext(
RecordPickerContext,
);
const { onRowSelectionChange, rowKey = 'id', ...others } = useTsp();
const { setVisible } = useActionContext();
return {
@ -62,7 +58,7 @@ const useTableSelectorProps = () => {
export const InternalPicker = observer(
(props: any) => {
const { value, multiple, onChange, quickUpload, selectFile, ...others } = props;
const { value, multiple, onChange, quickUpload, selectFile, shouldMountElement, ...others } = props;
const field: any = useField();
const fieldNames = useFieldNames(props);
const [visibleSelector, setVisibleSelector] = useState(false);
@ -176,7 +172,13 @@ export const InternalPicker = observer(
</RecordProvider>
)}
</Input.Group>
<ActionContextProvider value={{ openMode: 'drawer', visible: visibleSelector, setVisible: setVisibleSelector }}>
<ActionContextProvider
value={{
openMode: 'drawer',
visible: visibleSelector,
setVisible: setVisibleSelector,
}}
>
<RecordPickerProvider {...pickerProps}>
<CollectionProvider name={collectionField?.target}>
<FormProvider>

View File

@ -0,0 +1,87 @@
import { Popover } from 'antd';
import { css } from '@emotion/css';
import { EditOutlined } from '@ant-design/icons';
import { observer } from '@formily/react';
import React, { useContext, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ReadPrettyInternalViewer } from './InternalViewer';
import { InternalNester } from './InternalNester';
import { useAssociationFieldContext } from './hooks';
import { ActionContextProvider, ActionContext } from '../action/context';
export const InternaPopoverNester = observer(
(props) => {
const { options } = useAssociationFieldContext();
const [visible, setVisible] = useState(false);
const { t } = useTranslation();
const ref = useRef();
const nesterProps = {
...props,
shouldMountElement: true,
};
const content = (
<div
ref={ref}
style={{ minWidth: '600px', maxWidth: '800px', maxHeight: '440px', overflow: 'auto' }}
className={css`
min-width: 600px;
max-height: 440px;
overflow: auto;
.ant-card {
border: 0px;
}
`}
>
<InternalNester {...nesterProps} />
</div>
);
const titleProps = {
...props,
enableLink: true,
};
const getContainer = () => ref.current;
const ctx = useContext(ActionContext);
const modalProps = {
getContainer: getContainer,
};
return (
<ActionContextProvider value={{ ...ctx, modalProps }}>
<Popover
overlayStyle={{ padding: '0px' }}
content={content}
trigger="click"
placement="topLeft"
open={visible}
onOpenChange={(open) => setVisible(open)}
title={t(options?.uiSchema?.rawTitle)}
>
<span style={{ cursor: 'pointer', display: 'flex' }}>
<div
className={css`
max-width: 95%;
`}
>
<ReadPrettyInternalViewer {...titleProps} />
</div>
<EditOutlined style={{ display: 'inline-flex', marginLeft: '5px' }} />
</span>
</Popover>
{visible && (
<div
onClick={() => setVisible(false)}
className={css`
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: transparent;
z-index: 9999;
`}
/>
)}
</ActionContextProvider>
);
},
{ displayName: 'InternaPopoverNester' },
);

View File

@ -1,5 +1,5 @@
import { css } from '@emotion/css';
import { FormItem, FormLayout } from '@formily/antd-v5';
import { FormLayout } from '@formily/antd-v5';
import { RecursionField, observer, useField, useFieldSchema, SchemaOptionsContext } from '@formily/react';
import React, { useEffect } from 'react';
import { CollectionProvider } from '../../../collection-manager';
@ -22,7 +22,6 @@ export const InternalSubTable = observer(
const option = useSchemaOptionsContext();
const components = {
...option.components,
FormItem: (props) => <FormItem {...props} />,
'Radio.Group': Select,
'Checkbox.Group': (props) => <Select multiple={true} mode="multiple" {...props} />,
};
@ -38,9 +37,8 @@ export const InternalSubTable = observer(
.ant-checkbox-wrapper {
margin-left: 8px;
}
.ant-btn {
border: none !important;
box-shadow: none;
.ant-table {
margin: 0px !important;
}
}
`}

View File

@ -39,7 +39,6 @@ export const ReadPrettyInternalViewer: React.FC = observer(
const { designable } = useDesignable();
const { snapshot } = useActionContext();
const ellipsisWithTooltipRef = useRef<IEllipsisWithTooltipRef>();
const renderRecords = () =>
toArr(props.value).map((record, index, arr) => {
const val = toValue(compile(record?.[fieldNames?.label || 'label']), 'N/A');
@ -53,7 +52,7 @@ export const ReadPrettyInternalViewer: React.FC = observer(
<span>
{snapshot ? (
text
) : enableLink !== false ? (
) : enableLink !== false && !props.enableLink ? (
<a
onClick={(e) => {
e.stopPropagation();
@ -76,7 +75,6 @@ export const ReadPrettyInternalViewer: React.FC = observer(
</Fragment>
);
});
const renderWithoutTableFieldResourceProvider = () => (
<WithoutTableFieldResource.Provider value={true}>
<FormProvider>

View File

@ -29,6 +29,7 @@ const ToOneNester = (props) => {
const ToManyNester = observer(
(props) => {
console.log(props);
const fieldSchema = useFieldSchema();
const { options, field, allowMultiple, allowDissociate } = useAssociationFieldContext<ArrayField>();
const { t } = useTranslation();

View File

@ -4,7 +4,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { useCollectionFilterOptions, useCollectionManager } from '../../../collection-manager';
import { GeneralSchemaDesigner, SchemaSettings, isPatternDisabled, isShowDefaultValue } from '../../../schema-settings';
import { useCompile, useDesignable } from '../../hooks';
import { useCompile, useDesignable, useFieldModeOptions } from '../../hooks';
import { useAssociationFieldContext } from '../association-field/hooks';
import { removeNullCondition } from '../filter';
import { FilterDynamicComponent } from './FilterDynamicComponent';
@ -62,6 +62,10 @@ export const TableColumnDesigner = (props) => {
const defaultFilter = fieldSchema?.['x-component-props']?.service?.params?.filter || {};
const dataSource = useCollectionFilterOptions(collectionField?.target);
const isDateField = ['datetime', 'createdAt', 'updatedAt'].includes(collectionField?.interface);
const isAssociationField = ['obo', 'oho', 'o2o', 'o2m', 'm2m', 'm2o', 'snapshot'].includes(
collectionField?.interface,
);
const fieldModeOptions = useFieldModeOptions({ fieldSchema });
const fieldMode = fieldSchema?.['x-component-props']?.['mode'] || 'Select';
let readOnlyMode = 'editable';
if (fieldSchema['x-disabled'] === true) {
@ -250,32 +254,35 @@ export const TableColumnDesigner = (props) => {
}}
/>
)}
{readOnlyMode === 'read-pretty' &&
['linkTo', 'm2m', 'm2o', 'o2m', 'obo', 'oho', 'snapshot'].includes(collectionField?.interface) && (
<SchemaSettings.SelectItem
key="field-mode"
title={t('Field component')}
options={[
{ label: t('Title'), value: 'Select' },
{ label: t('Tag'), value: 'Tag' },
]}
value={fieldMode}
onChange={(mode) => {
const schema = {
['x-uid']: fieldSchema['x-uid'],
};
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
fieldSchema['x-component-props']['mode'] = mode;
schema['x-component-props'] = fieldSchema['x-component-props'];
field.componentProps = field.componentProps || {};
field.componentProps.mode = mode;
dn.emit('patch', {
schema,
});
dn.refresh();
}}
/>
)}
{isAssociationField && (
<SchemaSettings.SelectItem
key="field-mode"
title={t('Field component')}
options={
readOnlyMode === 'read-pretty'
? [
{ label: t('Title'), value: 'Select' },
{ label: t('Tag'), value: 'Tag' },
]
: fieldModeOptions
}
value={fieldMode}
onChange={(mode) => {
const schema = {
['x-uid']: fieldSchema['x-uid'],
};
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
fieldSchema['x-component-props']['mode'] = mode;
schema['x-component-props'] = fieldSchema['x-component-props'];
field.componentProps = field.componentProps || {};
field.componentProps.mode = mode;
dn.emit('patch', {
schema,
});
dn.refresh();
}}
/>
)}
{['Tag'].includes(fieldMode) && (
<SchemaSettings.SelectItem
@ -299,7 +306,6 @@ export const TableColumnDesigner = (props) => {
}}
/>
)}
{isSubTableColumn && !field.readPretty && !uiSchema?.['x-read-pretty'] && (
<SchemaSettings.SwitchItem
key="required"

View File

@ -3,11 +3,13 @@ import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useCollection, useCollectionManager } from '../../collection-manager';
export const useFieldModeOptions = () => {
export const useFieldModeOptions = (props?) => {
const { getCollectionJoinField, getCollection } = useCollectionManager();
const fieldSchema = useFieldSchema();
const currentFieldSchema = useFieldSchema();
const fieldSchema = props?.fieldSchema || currentFieldSchema;
const field = useField();
const isReadPretty = field.readPretty;
const isSubTableField = props?.fieldSchema;
const { getField } = useCollection();
const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
const { t } = useTranslation();
@ -16,7 +18,6 @@ export const useFieldModeOptions = () => {
if (!collectionField || !collectionField?.interface) {
return;
}
if (!['o2o', 'oho', 'obo', 'o2m', 'linkTo', 'm2o', 'm2m'].includes(collectionField.interface)) return;
const collection = getCollection(collectionField.target);
if (collection?.template === 'file') {
@ -29,10 +30,9 @@ export const useFieldModeOptions = () => {
: [
{ label: t('Select'), value: 'Select' },
{ label: t('Record picker'), value: 'Picker' },
{ label: t('File manager'), value: 'FileManager' },
!isSubTableField && { label: t('File manager'), value: 'FileManager' },
];
}
switch (collectionField.interface) {
case 'o2m':
return isReadPretty
@ -45,8 +45,9 @@ export const useFieldModeOptions = () => {
: [
{ label: t('Select'), value: 'Select' },
{ label: t('Record picker'), value: 'Picker' },
{ label: t('Sub-form'), value: 'Nester' },
{ label: t('Sub-table'), value: 'SubTable' },
!isSubTableField && { label: t('Sub-form'), value: 'Nester' },
{ label: t('Sub-form(Popover)'), value: 'PopoverNester' },
!isSubTableField && { label: t('Sub-table'), value: 'SubTable' },
];
case 'm2m':
return isReadPretty
@ -59,8 +60,9 @@ export const useFieldModeOptions = () => {
: [
{ label: t('Select'), value: 'Select' },
{ label: t('Record picker'), value: 'Picker' },
{ label: t('Sub-table'), value: 'SubTable' },
{ label: t('Sub-form'), value: 'Nester' },
!isSubTableField && { label: t('Sub-table'), value: 'SubTable' },
!isSubTableField && { label: t('Sub-form'), value: 'Nester' },
{ label: t('Sub-form(Popover)'), value: 'PopoverNester' },
];
case 'm2o':
case 'linkTo':
@ -73,7 +75,8 @@ export const useFieldModeOptions = () => {
: [
{ label: t('Select'), value: 'Select' },
{ label: t('Record picker'), value: 'Picker' },
{ label: t('Sub-form'), value: 'Nester' },
!isSubTableField && { label: t('Sub-form'), value: 'Nester' },
{ label: t('Sub-form(Popover)'), value: 'PopoverNester' },
];
default:
@ -86,9 +89,10 @@ export const useFieldModeOptions = () => {
: [
{ label: t('Select'), value: 'Select' },
{ label: t('Record picker'), value: 'Picker' },
{ label: t('Sub-form'), value: 'Nester' },
!isSubTableField && { label: t('Sub-form'), value: 'Nester' },
{ label: t('Sub-form(Popover)'), value: 'PopoverNester' },
];
}
}, [t, collectionField?.interface, label]);
return fieldModeOptions;
return (fieldModeOptions || []).filter(Boolean);
};

View File

@ -290,6 +290,17 @@ export class Database extends EventEmitter implements AsyncEmitter {
}),
});
this.migrator = new Umzug({
logger: migratorOptions.logger || console,
migrations: this.migrations.callback(),
context,
storage: new SequelizeStorage({
modelName: `${this.options.tablePrefix || ''}migrations`,
...migratorOptions.storage,
sequelize: this.sequelize,
}),
});
this.initListener();
patchSequelizeQueryInterface(this);
}