feat: support Others option in popup (#4015)

* feat: support Others option in popup

* chore: hide Other records in popup for edit form

* chore: rename 'Others' to 'Other records'

* fix: in other records, the data table does not need to filter itself

* feat: optimize title for association block

* fix: template

* fix: block title

* chore: fix e2e

* fix: should use compile

* fix: remove useVisible

* test: add e2e
This commit is contained in:
Zeke Zhang 2024-04-12 19:14:18 +08:00 committed by GitHub
parent 1658415402
commit 17793c2ab9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 802 additions and 252 deletions

View File

@ -496,6 +496,7 @@
"edit title": "edit title",
"Turn pages": "Turn pages",
"Others": "Others",
"Other records": "Other records",
"Save as template": "Save as template",
"Save as block template": "Save as block template",
"Block templates": "Block templates",

View File

@ -467,6 +467,7 @@
"edit title": "Editar título",
"Turn pages": "Pasar páginas",
"Others": "Otros",
"Other records": "Otros registros",
"Save as template": "Guardar como plantilla",
"Save as block template": "Guardar como plantilla de bloque",
"Block templates": "Bloquear plantillas",

View File

@ -482,6 +482,7 @@
"edit title": "modifier le titre",
"Turn pages": "Tourner les pages",
"Others": "Autres",
"Other records": "Autres enregistrements",
"Save as template": "Enregistrer en tant que modèle",
"Save as block template": "Enregistrer en tant que modèle de bloc",
"Block templates": "Modèles de bloc",

View File

@ -393,7 +393,8 @@
"Add card": "カードを追加",
"edit title": "タイトルを編集",
"Turn pages": "ページをめくる",
"Others": "Others",
"Others": "その他",
"Other records": "他のレコード",
"Save as template": "テンプレートとして保存",
"Save as block template": "ブロックテンプレートとして保存",
"Block templates": "ブロックテンプレート",

View File

@ -514,6 +514,7 @@
"edit title": "제목 수정",
"Turn pages": "페이지 넘김",
"Others": "기타",
"Other records": "기타 레코드",
"Save as template": "템플릿으로 저장",
"Save as block template": "블록 템플릿으로 저장",
"Block templates": "블록 템플릿",

View File

@ -430,6 +430,7 @@
"edit title": "editar título",
"Turn pages": "Virar páginas",
"Others": "Outros",
"Other records": "Outros registros",
"Save as template": "Salvar como modelo",
"Save as block template": "Salvar como modelo de bloco",
"Block templates": "Modelos de bloco",

View File

@ -334,6 +334,7 @@
"edit title": "изменить заголовок",
"Turn pages": "Перелистывать страницы",
"Others": "Другие",
"Other records": "Другие записи",
"Save as template": "Сохранить как шаблон",
"Save as block template": "Сохранить как шаблон Блока",
"Block templates": "Шаблоны Блока",

View File

@ -333,6 +333,7 @@
"edit title": "başlığı düzenle",
"Turn pages": "Sayfaları çevir",
"Others": "Diğerleri",
"Other records": "Diğer kayıtlar",
"Save as template": "Şablon olarak kaydet",
"Save as block template": "Blok şablonu olarak kaydet",
"Block templates": "Blok şablonları",

View File

@ -484,6 +484,7 @@
"edit title": "редагувати назву",
"Turn pages": "Переключати сторінки",
"Others": "Інші",
"Other records": "Інші записи",
"Save as template": "Зберегти як шаблон",
"Save as block template": "Зберегти як шаблон блока",
"Block templates": "Шаблони блоків",

View File

@ -517,6 +517,7 @@
"edit title": "修改标题",
"Turn pages": "翻页",
"Others": "其他",
"Other records": "其他记录",
"Save as template": "保存为模板",
"Save as block template": "保存为区块模板",
"Block templates": "区块模板",

View File

@ -514,6 +514,7 @@
"edit title": "編輯標題",
"Turn pages": "翻頁",
"Others": "其他",
"Other records": "其他記錄",
"Save as template": "儲存為模板",
"Save as block template": "儲存為區塊模板",
"Block templates": "區塊模板",

View File

@ -1,19 +1,58 @@
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useCollection_deprecated } from '../../collection-manager';
import { useCollectionManager, useDataBlockProps } from '../../data-source';
import { useCollection } from '../../data-source/collection/CollectionProvider';
import { useCompile } from '../../schema-component';
import { SchemaToolbar } from '../../schema-settings/GeneralSchemaDesigner';
import { useSchemaTemplate } from '../../schema-templates';
export const BlockSchemaToolbar = (props) => {
const { t } = useTranslation();
const { name, title } = useCollection_deprecated();
const cm = useCollectionManager();
let { name: currentCollectionName, title: currentCollectionTitle } = useCollection();
const template = useSchemaTemplate();
const { association } = useDataBlockProps() || {};
const compile = useCompile();
if (association) {
const [collectionName] = association.split('.');
const { name, title } = cm.getCollection(collectionName);
currentCollectionName = name;
currentCollectionTitle = title;
}
const associationField = cm.getCollectionField(association);
const templateName = ['FormItem', 'ReadPrettyFormItem'].includes(template?.componentName)
? `${template?.name} ${t('(Fields only)')}`
: template?.name;
const toolbarTitle = useMemo(() => {
return [title || name, templateName].filter(Boolean);
}, [name, templateName, title]);
return [
getCollectionTitle({
collectionTitle: currentCollectionTitle,
collectionName: currentCollectionName,
associationField,
compile,
}),
templateName,
].filter(Boolean);
}, [compile, currentCollectionTitle, currentCollectionName, associationField, templateName]);
return <SchemaToolbar title={toolbarTitle} {...props} />;
};
function getCollectionTitle(arg0: {
collectionTitle: string;
collectionName: string;
associationField: any;
compile: any;
}) {
const { collectionTitle, collectionName, associationField, compile } = arg0;
if (associationField) {
return `${compile(collectionTitle || collectionName)} > ${compile(
associationField.uiSchema?.title || associationField.name,
)}`;
}
return collectionTitle || collectionName;
}

View File

@ -1,9 +1,9 @@
import { TableOutlined } from '@ant-design/icons';
import React from 'react';
import React, { useCallback } from 'react';
import { useSchemaInitializer, useSchemaInitializerItem } from '../../../../application';
import { useCollectionManager_deprecated } from '../../../../collection-manager';
import { DataBlockInitializer } from '../../../../schema-initializer/items/DataBlockInitializer';
import { Collection, CollectionFieldOptions } from '../../../../data-source/collection/Collection';
import { DataBlockInitializer } from '../../../../schema-initializer/items/DataBlockInitializer';
import { createDetailsWithPaginationUISchema } from './createDetailsWithPaginationUISchema';
export const DetailsBlockInitializer = ({
@ -36,9 +36,8 @@ export const DetailsBlockInitializer = ({
showAssociationFields?: boolean;
hideChildrenIfSingleCollection?: boolean;
}) => {
const { insert } = useSchemaInitializer();
const { getCollection } = useCollectionManager_deprecated();
const itemConfig = useSchemaInitializerItem();
const { createDetailsBlock } = useCreateDetailsBlock();
return (
<DataBlockInitializer
{...itemConfig}
@ -48,19 +47,7 @@ export const DetailsBlockInitializer = ({
if (createBlockSchema) {
return createBlockSchema(options);
}
const { item } = options;
const collection = getCollection(item.name, item.dataSource);
const schema = createDetailsWithPaginationUISchema({
collectionName: item.name,
dataSource: item.dataSource,
rowKey: collection.filterTargetKey || 'id',
hideActionInitializer: !(
(collection.template !== 'view' || collection?.writableView) &&
collection.template !== 'sql'
),
});
insert(schema);
createDetailsBlock(options);
}}
onlyCurrentDataSource={!!onlyCurrentDataSource}
hideSearch={hideSearch}
@ -71,3 +58,27 @@ export const DetailsBlockInitializer = ({
/>
);
};
export const useCreateDetailsBlock = () => {
const { insert } = useSchemaInitializer();
const { getCollection } = useCollectionManager_deprecated();
const createDetailsBlock = useCallback(
({ item }) => {
const collection = getCollection(item.name, item.dataSource);
const schema = createDetailsWithPaginationUISchema({
collectionName: item.name,
dataSource: item.dataSource,
rowKey: collection.filterTargetKey || 'id',
hideActionInitializer: !(
(collection.template !== 'view' || collection?.writableView) &&
collection.template !== 'sql'
),
});
insert(schema);
},
[getCollection, insert],
);
return { createDetailsBlock };
};

View File

@ -1,4 +1,5 @@
import { createBlockInPage, expect, oneEmptyDetailsBlock, test } from '@nocobase/test/e2e';
import { oneEmptyTableWithUsers } from './templatesOfBug';
test.describe('where multi data details block can be added', () => {
test('page', async ({ page, mockPage }) => {
@ -7,6 +8,35 @@ test.describe('where multi data details block can be added', () => {
await createBlockInPage(page, 'Details');
await expect(page.getByLabel('block-item-CardItem-users-details')).toBeVisible();
});
test('popup', async ({ page, mockPage }) => {
await mockPage(oneEmptyTableWithUsers).goto();
// 1. 打开弹窗,通过 Associated records 添加一个详情区块
await page.getByLabel('action-Action.Link-View').click();
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'table Details right' }).hover();
await page.getByRole('menuitem', { name: 'Associated records right' }).hover();
await page.getByRole('menuitem', { name: 'Roles' }).click();
await page.mouse.move(300, 0);
await page.getByLabel('schema-initializer-Grid-details:configureFields-roles').hover();
await page.getByRole('menuitem', { name: 'Role UID' }).click();
await page.mouse.move(300, 0);
await expect(page.getByLabel('block-item-CollectionField-').getByText('admin')).toBeVisible();
// 2. 打开弹窗,通过 Other records 添加一个详情区块
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'table Details right' }).hover();
await page.getByRole('menuitem', { name: 'Other records right' }).hover();
await page.getByRole('menuitem', { name: 'Users' }).click();
await page.mouse.move(300, 0);
await page.getByLabel('schema-initializer-Grid-details:configureFields-users').click();
await page.getByRole('menuitem', { name: 'Nickname' }).click();
await page.mouse.move(300, 0);
await expect(
page.getByLabel('block-item-CollectionField-users-details-users.nickname-Nickname').getByText('Super Admin'),
).toBeVisible();
});
});
test.describe('configure fields', () => {

View File

@ -0,0 +1,222 @@
export const oneEmptyTableWithUsers = {
pageSchema: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Page',
properties: {
zvj8cbqvt05: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'page:addBlock',
properties: {
j0zzcf3k2vc: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '0.21.0-alpha.6',
properties: {
pn0pgjxjlz2: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
'x-app-version': '0.21.0-alpha.6',
properties: {
o9zor60xpvi: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'TableBlockProvider',
'x-acl-action': 'users:list',
'x-use-decorator-props': 'useTableBlockDecoratorProps',
'x-decorator-props': {
collection: 'users',
dataSource: 'main',
action: 'list',
params: {
pageSize: 20,
},
rowKey: 'id',
showIndex: true,
dragSort: false,
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:table',
'x-component': 'CardItem',
'x-filter-targets': [],
'x-app-version': '0.21.0-alpha.6',
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-initializer': 'table:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 'var(--nb-spacing)',
},
},
'x-app-version': '0.21.0-alpha.6',
'x-uid': 'dnnfz8xyqh9',
'x-async': false,
'x-index': 1,
},
'3rr559rnt6k': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'array',
'x-initializer': 'table:configureColumns',
'x-component': 'TableV2',
'x-use-component-props': 'useTableBlockProps',
'x-component-props': {
rowKey: 'id',
rowSelection: {
type: 'checkbox',
},
},
'x-app-version': '0.21.0-alpha.6',
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{ t("Actions") }}',
'x-action-column': 'actions',
'x-decorator': 'TableV2.Column.ActionBar',
'x-component': 'TableV2.Column',
'x-designer': 'TableV2.ActionColumnDesigner',
'x-initializer': 'table:configureItemActions',
'x-app-version': '0.21.0-alpha.6',
properties: {
cf7dj1iffh3: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'DndContext',
'x-component': 'Space',
'x-component-props': {
split: '|',
},
'x-app-version': '0.21.0-alpha.6',
properties: {
oealnj6s2rw: {
'x-uid': 'e32p6hfhh35',
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: 'View record',
'x-action': 'view',
'x-toolbar': 'ActionSchemaToolbar',
'x-settings': 'actionSettings:view',
'x-component': 'Action.Link',
'x-component-props': {
openMode: 'drawer',
danger: false,
},
'x-decorator': 'ACLActionProvider',
'x-designer-props': {
linkageAction: true,
},
properties: {
drawer: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{ t("View record") }}',
'x-component': 'Action.Container',
'x-component-props': {
className: 'nb-action-popup',
},
properties: {
tabs: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Tabs',
'x-component-props': {},
'x-initializer': 'popup:addTab',
properties: {
tab1: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{t("Details")}}',
'x-component': 'Tabs.TabPane',
'x-designer': 'Tabs.Designer',
'x-component-props': {},
properties: {
grid: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'popup:common:addBlock',
'x-uid': 'iuh8edqjzjf',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'c7s6mbv0ifc',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'l7mf3lk2rfi',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '96mjki0iss6',
'x-async': false,
'x-index': 1,
},
},
'x-async': false,
'x-index': 1,
},
},
'x-uid': '4wz5e2y5mi3',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'jursnceu1wj',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'zysxygy87b3',
'x-async': false,
'x-index': 2,
},
},
'x-uid': '1cz65li561a',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '3p85gkub5m6',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'gqwx7acdf2r',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'jzntdh10ctc',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'jr9hile0rrx',
'x-async': true,
'x-index': 1,
},
};

View File

@ -11,9 +11,10 @@ export const FormBlockInitializer = ({
hideSearch,
createBlockSchema,
componentType = 'FormItem',
templateWrap,
templateWrap: customizeTemplateWrap,
showAssociationFields,
hideChildrenIfSingleCollection,
hideOtherRecordsInPopup,
}: {
filterCollections: (options: { collection?: Collection; associationField?: CollectionFieldOptions }) => boolean;
onlyCurrentDataSource: boolean;
@ -33,25 +34,22 @@ export const FormBlockInitializer = ({
) => any;
showAssociationFields?: boolean;
hideChildrenIfSingleCollection?: boolean;
/**
* Other records
*/
hideOtherRecordsInPopup?: boolean;
}) => {
const { insert } = useSchemaInitializer();
const itemConfig = useSchemaInitializerItem();
const { isCusomeizeCreate } = itemConfig;
const { createFormBlock, templateWrap } = useCreateFormBlock();
const onCreateFormBlockSchema = useCallback(
({ item }) => {
(options) => {
if (createBlockSchema) {
return createBlockSchema({ item });
return createBlockSchema(options);
}
insert(
createCreateFormBlockUISchema({
collectionName: item.collectionName || item.name,
dataSource: item.dataSource,
isCusomeizeCreate,
}),
);
createFormBlock(options);
},
[createBlockSchema, insert, isCusomeizeCreate],
[createBlockSchema, createFormBlock],
);
return (
@ -59,21 +57,12 @@ export const FormBlockInitializer = ({
{...itemConfig}
icon={<FormOutlined />}
componentType={componentType}
templateWrap={(templateSchema, { item }) => {
if (templateWrap) {
return templateWrap(templateSchema, { item });
templateWrap={(templateSchema, options) => {
if (customizeTemplateWrap) {
return customizeTemplateWrap(templateSchema, options);
}
const schema = createCreateFormBlockUISchema({
isCusomeizeCreate,
dataSource: item.dataSource,
templateSchema: templateSchema,
collectionName: item.name,
});
if (item.template && item.mode === 'reference') {
schema['x-template-key'] = item.template.key;
}
return schema;
return templateWrap(templateSchema, options);
}}
onCreateBlockSchema={onCreateFormBlockSchema}
filter={filterCollections}
@ -81,6 +70,41 @@ export const FormBlockInitializer = ({
hideSearch={hideSearch}
showAssociationFields={showAssociationFields}
hideChildrenIfSingleCollection={hideChildrenIfSingleCollection}
hideOtherRecordsInPopup={hideOtherRecordsInPopup}
/>
);
};
export const useCreateFormBlock = () => {
const { insert } = useSchemaInitializer();
const itemConfig = useSchemaInitializerItem();
const { isCusomeizeCreate: isCustomizeCreate } = itemConfig;
const createFormBlock = ({ item }) => {
insert(
createCreateFormBlockUISchema({
collectionName: item.collectionName || item.name,
dataSource: item.dataSource,
isCusomeizeCreate: isCustomizeCreate,
}),
);
};
const templateWrap = (templateSchema, { item }) => {
const schema = createCreateFormBlockUISchema({
isCusomeizeCreate: isCustomizeCreate,
dataSource: item.dataSource,
templateSchema: templateSchema,
collectionName: item.name,
});
if (item.template && item.mode === 'reference') {
schema['x-template-key'] = item.template.key;
}
return schema;
};
return {
createFormBlock,
templateWrap,
};
};

View File

@ -1,6 +1,7 @@
import { createBlockInPage, expect, oneEmptyForm, test } from '@nocobase/test/e2e';
import { T3106, T3469 } from './templatesOfBug';
import { uid } from '@formily/shared';
import { createBlockInPage, expect, oneEmptyForm, test } from '@nocobase/test/e2e';
import { oneEmptyTableWithUsers } from '../../../details-multi/__e2e__/templatesOfBug';
import { T3106, T3469 } from './templatesOfBug';
test.describe('where creation form block can be added', () => {
test('page', async ({ page, mockPage }) => {
@ -10,6 +11,27 @@ test.describe('where creation form block can be added', () => {
await createBlockInPage(page, 'Form');
await expect(page.getByLabel('block-item-CardItem-users-form')).toBeVisible();
});
test('popup', async ({ page, mockPage }) => {
await mockPage(oneEmptyTableWithUsers).goto();
// 1. 打开弹窗,通过 Associated records 创建一个创建表单区块
await page.getByLabel('action-Action.Link-View').click();
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'form Form (Add new) right' }).hover();
await page.getByRole('menuitem', { name: 'Associated records right' }).hover();
await page.getByRole('menuitem', { name: 'Roles' }).click();
await page.mouse.move(300, 0);
await expect(page.getByLabel('block-item-CardItem-roles-form')).toBeVisible();
// 2. 通过 Other records 创建一个创建表单区块
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'form Form (Add new) right' }).hover();
await page.getByRole('menuitem', { name: 'Other records right' }).hover();
await page.getByRole('menuitem', { name: 'Users' }).click();
await page.mouse.move(300, 0);
await expect(page.getByLabel('block-item-CardItem-users-form')).toBeVisible();
});
});
test.describe('configure fields', () => {

View File

@ -2,9 +2,9 @@ import { OrderedListOutlined } from '@ant-design/icons';
import React from 'react';
import { useSchemaInitializer, useSchemaInitializerItem } from '../../../../application';
import { useCollectionManager_deprecated } from '../../../../collection-manager';
import { createGridCardBlockSchema } from './createGridCardBlockSchema';
import { DataBlockInitializer } from '../../../../schema-initializer/items/DataBlockInitializer';
import { Collection, CollectionFieldOptions } from '../../../../data-source/collection/Collection';
import { DataBlockInitializer } from '../../../../schema-initializer/items/DataBlockInitializer';
import { createGridCardBlockUISchema } from './createGridCardBlockUISchema';
export const GridCardBlockInitializer = ({
filterCollections,
@ -33,26 +33,19 @@ export const GridCardBlockInitializer = ({
) => any;
showAssociationFields?: boolean;
}) => {
const { insert } = useSchemaInitializer();
const { getCollection } = useCollectionManager_deprecated();
const itemConfig = useSchemaInitializerItem();
const { createGridCardBlock } = useCreateGridCardBlock();
return (
<DataBlockInitializer
{...itemConfig}
icon={<OrderedListOutlined />}
componentType={'GridCard'}
onCreateBlockSchema={async ({ item }) => {
onCreateBlockSchema={async (options) => {
if (createBlockSchema) {
return createBlockSchema({ item });
return createBlockSchema(options);
}
const collection = getCollection(item.name, item.dataSource);
const schema = createGridCardBlockSchema({
collectionName: item.name,
dataSource: item.dataSource,
rowKey: collection.filterTargetKey || 'id',
});
insert(schema);
createGridCardBlock(options);
}}
onlyCurrentDataSource={onlyCurrentDataSource}
hideSearch={hideSearch}
@ -61,3 +54,20 @@ export const GridCardBlockInitializer = ({
/>
);
};
export const useCreateGridCardBlock = () => {
const { insert } = useSchemaInitializer();
const { getCollection } = useCollectionManager_deprecated();
const createGridCardBlock = ({ item }) => {
const collection = getCollection(item.name, item.dataSource);
const schema = createGridCardBlockUISchema({
collectionName: item.name,
dataSource: item.dataSource,
rowKey: collection.filterTargetKey || 'id',
});
insert(schema);
};
return { createGridCardBlock };
};

View File

@ -1,4 +1,5 @@
import { createBlockInPage, expect, oneEmptyGridCardBlock, test } from '@nocobase/test/e2e';
import { oneEmptyTableWithUsers } from '../../details-multi/__e2e__/templatesOfBug';
test.describe('where grid card block can be added', () => {
test('page', async ({ page, mockPage }) => {
@ -9,6 +10,37 @@ test.describe('where grid card block can be added', () => {
await expect(page.getByLabel('block-item-BlockItem-users-grid-card')).toBeVisible();
});
test('popup', async ({ page, mockPage }) => {
await mockPage(oneEmptyTableWithUsers).goto();
// 1. 打开弹窗,通过 Associated records 创建一个列表区块
await page.getByLabel('action-Action.Link-View').click();
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'ordered-list Grid Card right' }).hover();
await page.getByRole('menuitem', { name: 'Associated records right' }).hover();
await page.getByRole('menuitem', { name: 'Roles' }).click();
await page.mouse.move(300, 0);
await page.getByLabel('schema-initializer-Grid-').nth(1).hover();
await page.getByRole('menuitem', { name: 'Role name' }).click();
await page.mouse.move(300, 0);
await expect(page.getByText('Root')).toBeVisible();
await expect(page.getByText('Admin')).toBeVisible();
await expect(page.getByText('Member')).toBeVisible();
// 2. 通过 Other records 创建一个列表区块
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'ordered-list Grid Card right' }).hover();
await page.getByRole('menuitem', { name: 'Other records right' }).hover();
await page.getByRole('menuitem', { name: 'Users' }).click();
await page.mouse.move(300, 0);
await page.getByLabel('schema-initializer-Grid-details:configureFields-users').hover();
await page.getByRole('menuitem', { name: 'Nickname' }).click();
await page.mouse.move(300, 0);
await expect(
page.getByLabel('block-item-CollectionField-users-grid-card-users.nickname-Nickname').getByText('Super Admin'),
).toBeVisible();
});
test('data selector popup', async ({ page, mockPage }) => {});
});

View File

@ -1,4 +1,4 @@
import { createGridCardBlockSchema } from '../createGridCardBlockSchema';
import { createGridCardBlockUISchema } from '../createGridCardBlockUISchema';
describe('createGridCardBlockSchema', () => {
test('should return the correct schema', () => {
@ -10,7 +10,7 @@ describe('createGridCardBlockSchema', () => {
rowKey: 'testRowKey',
};
const schema = createGridCardBlockSchema(options);
const schema = createGridCardBlockUISchema(options);
expect(schema).toMatchInlineSnapshot(`
{
@ -86,7 +86,7 @@ describe('createGridCardBlockSchema', () => {
rowKey: 'testRowKey',
};
const schema = createGridCardBlockSchema(options);
const schema = createGridCardBlockUISchema(options);
expect(schema).toMatchInlineSnapshot(`
{

View File

@ -1,6 +1,6 @@
import { ISchema } from '@formily/react';
export const createGridCardBlockSchema = (options: {
export const createGridCardBlockUISchema = (options: {
dataSource: string;
collectionName?: string;
association?: string;

View File

@ -2,9 +2,9 @@ import { OrderedListOutlined } from '@ant-design/icons';
import React from 'react';
import { useSchemaInitializer, useSchemaInitializerItem } from '../../../../application';
import { useCollectionManager_deprecated } from '../../../../collection-manager';
import { createListBlockSchema } from './createListBlockSchema';
import { DataBlockInitializer } from '../../../../schema-initializer/items/DataBlockInitializer';
import { Collection, CollectionFieldOptions } from '../../../../data-source/collection/Collection';
import { DataBlockInitializer } from '../../../../schema-initializer/items/DataBlockInitializer';
import { createListBlockUISchema } from './createListBlockUISchema';
export const ListBlockInitializer = ({
filterCollections,
@ -33,26 +33,19 @@ export const ListBlockInitializer = ({
) => any;
showAssociationFields?: boolean;
}) => {
const { getCollection } = useCollectionManager_deprecated();
const { insert } = useSchemaInitializer();
const itemConfig = useSchemaInitializerItem();
const { createListBlock } = useCreateListBlock();
return (
<DataBlockInitializer
{...itemConfig}
icon={<OrderedListOutlined />}
componentType={'List'}
onCreateBlockSchema={async ({ item }) => {
onCreateBlockSchema={async (options) => {
if (createBlockSchema) {
return createBlockSchema({ item });
return createBlockSchema(options);
}
const collection = getCollection(item.name, item.dataSource);
const schema = createListBlockSchema({
collectionName: item.name,
dataSource: item.dataSource,
rowKey: collection.filterTargetKey || 'id',
});
insert(schema);
createListBlock(options);
}}
onlyCurrentDataSource={onlyCurrentDataSource}
hideSearch={hideSearch}
@ -61,3 +54,20 @@ export const ListBlockInitializer = ({
/>
);
};
export const useCreateListBlock = () => {
const { getCollection } = useCollectionManager_deprecated();
const { insert } = useSchemaInitializer();
const createListBlock = ({ item }) => {
const collection = getCollection(item.name, item.dataSource);
const schema = createListBlockUISchema({
collectionName: item.name,
dataSource: item.dataSource,
rowKey: collection.filterTargetKey || 'id',
});
insert(schema);
};
return { createListBlock };
};

View File

@ -1,4 +1,5 @@
import { createBlockInPage, expect, oneEmptyListBlock, test } from '@nocobase/test/e2e';
import { oneEmptyTableWithUsers } from '../../details-multi/__e2e__/templatesOfBug';
test.describe('where list block can be added', () => {
test('page', async ({ page, mockPage }) => {
@ -8,6 +9,37 @@ test.describe('where list block can be added', () => {
await createBlockInPage(page, 'List');
await expect(page.getByLabel('block-item-CardItem-users-list')).toBeVisible();
});
test('popup', async ({ page, mockPage }) => {
await mockPage(oneEmptyTableWithUsers).goto();
// 1. 打开弹窗,通过 Associated records 创建一个列表区块
await page.getByLabel('action-Action.Link-View').click();
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'ordered-list List right' }).hover();
await page.getByRole('menuitem', { name: 'Associated records right' }).hover();
await page.getByRole('menuitem', { name: 'Roles' }).click();
await page.mouse.move(300, 0);
await page.getByLabel('schema-initializer-Grid-').nth(1).hover();
await page.getByRole('menuitem', { name: 'Role name' }).click();
await page.mouse.move(300, 0);
await expect(page.getByLabel('block-item-CollectionField-').getByText('Root')).toBeVisible();
await expect(page.getByLabel('block-item-CollectionField-').getByText('Admin')).toBeVisible();
await expect(page.getByLabel('block-item-CollectionField-').getByText('Member')).toBeVisible();
// 2. 通过 Other records 创建一个列表区块
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'ordered-list List right' }).hover();
await page.getByRole('menuitem', { name: 'Other records right' }).hover();
await page.getByRole('menuitem', { name: 'Users' }).click();
await page.mouse.move(300, 0);
await page.getByLabel('schema-initializer-Grid-details:configureFields-users').hover();
await page.getByRole('menuitem', { name: 'Nickname' }).click();
await page.mouse.move(300, 0);
await expect(
page.getByLabel('block-item-CollectionField-users-list-users.nickname-Nickname').getByText('Super Admin'),
).toBeVisible();
});
});
test.describe('configure global actions', () => {

View File

@ -1,4 +1,4 @@
import { createListBlockSchema } from '../createListBlockSchema';
import { createListBlockUISchema } from '../createListBlockUISchema';
describe('createListBlockSchema', () => {
test('should return the correct schema', () => {
@ -20,7 +20,7 @@ describe('createListBlockSchema', () => {
rowKey: 'id',
};
const schema = createListBlockSchema(options);
const schema = createListBlockUISchema(options);
expect(schema).toMatchInlineSnapshot(`
{

View File

@ -1,6 +1,6 @@
import { ISchema } from '@formily/react';
export const createListBlockSchema = (options: {
export const createListBlockUISchema = (options: {
dataSource: string;
collectionName?: string;
association?: string;

View File

@ -1,9 +1,9 @@
import { TableOutlined } from '@ant-design/icons';
import React from 'react';
import { useSchemaInitializer, useSchemaInitializerItem } from '../../../../application/schema-initializer/context';
import { useCollectionManager_deprecated } from '../../../../collection-manager/hooks/useCollectionManager_deprecated';
import { DataBlockInitializer } from '../../../../schema-initializer/items/DataBlockInitializer';
import React from 'react';
import { Collection, CollectionFieldOptions } from '../../../../data-source/collection/Collection';
import { DataBlockInitializer } from '../../../../schema-initializer/items/DataBlockInitializer';
import { createTableBlockUISchema } from './createTableBlockUISchema';
export const TableBlockInitializer = ({
@ -28,26 +28,19 @@ export const TableBlockInitializer = ({
) => any;
showAssociationFields?: boolean;
}) => {
const { insert } = useSchemaInitializer();
const { getCollection } = useCollectionManager_deprecated();
const itemConfig = useSchemaInitializerItem();
const { createTableBlock } = useCreateTableBlock();
return (
<DataBlockInitializer
{...itemConfig}
icon={<TableOutlined />}
componentType={'Table'}
onCreateBlockSchema={async ({ item }) => {
onCreateBlockSchema={async (options) => {
if (createBlockSchema) {
return createBlockSchema({ item });
return createBlockSchema(options);
}
const collection = getCollection(item.name, item.dataSource);
const schema = createTableBlockUISchema({
collectionName: item.name,
dataSource: item.dataSource,
rowKey: collection.filterTargetKey || 'id',
});
insert(schema);
createTableBlock(options);
}}
onlyCurrentDataSource={onlyCurrentDataSource}
hideSearch={hideSearch}
@ -56,3 +49,20 @@ export const TableBlockInitializer = ({
/>
);
};
export const useCreateTableBlock = () => {
const { insert } = useSchemaInitializer();
const { getCollection } = useCollectionManager_deprecated();
const createTableBlock = ({ item }) => {
const collection = getCollection(item.name, item.dataSource);
const schema = createTableBlockUISchema({
collectionName: item.name,
dataSource: item.dataSource,
rowKey: collection.filterTargetKey || 'id',
});
insert(schema);
};
return { createTableBlock };
};

View File

@ -50,6 +50,17 @@ test.describe('where table block can be added', () => {
.getByLabel('block-item-CardItem-parentTargetCollection-table')
.getByText(childRecord.parentAssociationField[0].parentTargetText),
).toBeVisible();
// 通过 Other records 创建一个表格区块
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'table Table right' }).hover();
await page.getByRole('menuitem', { name: 'Other records right' }).hover();
await page.getByRole('menuitem', { name: 'Users' }).click();
await page.mouse.move(300, 0);
await page.getByLabel('schema-initializer-TableV2-table:configureColumns-users').hover();
await page.getByRole('menuitem', { name: 'Nickname' }).click();
await page.mouse.move(300, 0);
await expect(page.getByRole('button', { name: 'Super Admin' })).toBeVisible();
});
});

View File

@ -238,9 +238,10 @@ test.describe('add blocks to the popup', () => {
// 打开弹窗
await page.getByLabel('action-Action.Link-View-view-roles-table-root').click();
// 直接点击 Details 选项创建详情区块
// 点击 Details -> Current record 选项创建详情区块
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'table Details' }).click();
await page.getByRole('menuitem', { name: 'table Details' }).hover();
await page.getByRole('menuitem', { name: 'Current record' }).click();
await page.getByLabel('schema-initializer-Grid-details:configureFields-roles').hover();
await page.getByRole('menuitem', { name: 'Role UID' }).click();
await expect(page.getByLabel('block-item-CollectionField-Role').getByText('root')).toBeVisible();
@ -251,6 +252,7 @@ test.describe('add blocks to the popup', () => {
await page.getByRole('menuitem', { name: 'form Form (Edit)' }).click();
await page.getByLabel('schema-initializer-Grid-form:').hover();
await page.getByRole('menuitem', { name: 'Role UID' }).click();
await page.mouse.move(300, 0);
await expect(
page.getByLabel('block-item-CollectionField-roles-form-roles.name-Role UID').getByRole('textbox'),
).toHaveValue('root');

View File

@ -11,9 +11,14 @@ import {
useCreateAssociationListBlock,
useCreateAssociationTableBlock,
useCreateEditFormBlock,
useCreateFormBlock,
useCreateTableBlock,
} from '../..';
import { CompatibleSchemaInitializer } from '../../application/schema-initializer/CompatibleSchemaInitializer';
import { useCreateDetailsBlock } from '../../modules/blocks/data-blocks/details-multi/DetailsBlockInitializer';
import { useCreateSingleDetailsSchema } from '../../modules/blocks/data-blocks/details-single/RecordReadPrettyFormBlockInitializer';
import { useCreateGridCardBlock } from '../../modules/blocks/data-blocks/grid-card/GridCardBlockInitializer';
import { useCreateListBlock } from '../../modules/blocks/data-blocks/list/ListBlockInitializer';
import { gridRowColWrap } from '../utils';
const recursiveParent = (schema: Schema) => {
@ -52,9 +57,13 @@ function useRecordBlocks() {
createAssociationDetailsWithoutPagination,
templateWrap: templateWrapOfAssociationDetailsWithoutPagination,
} = useCreateAssociationDetailsWithoutPagination();
const { createDetailsBlock } = useCreateDetailsBlock();
const collectionsNeedToDisplay = [currentCollection, ...collectionsWithView];
const createBlockSchema = useCallback(
({ item }) => {
({ item, fromOthersInPopup }) => {
if (fromOthersInPopup) {
return createDetailsBlock({ item });
}
if (item.associationField) {
if (['hasOne', 'belongsTo'].includes(item.associationField.type)) {
return createAssociationDetailsWithoutPagination({ item });
@ -63,7 +72,12 @@ function useRecordBlocks() {
}
return createSingleDetailsSchema({ item });
},
[createAssociationDetailsBlock, createAssociationDetailsWithoutPagination, createSingleDetailsSchema],
[
createAssociationDetailsBlock,
createAssociationDetailsWithoutPagination,
createDetailsBlock,
createSingleDetailsSchema,
],
);
return {
filterCollections({ collection, associationField }) {
@ -103,7 +117,7 @@ function useRecordBlocks() {
dataSource: collection.dataSource,
useComponentProps() {
const currentCollection = useCollection_deprecated();
const { createEditFormBlock, templateWrap } = useCreateEditFormBlock();
const { createEditFormBlock, templateWrap: templateWrapEdit } = useCreateEditFormBlock();
const collectionsNeedToDisplay = [currentCollection, ...collectionsWithView];
return {
@ -115,9 +129,10 @@ function useRecordBlocks() {
},
onlyCurrentDataSource: true,
hideSearch: true,
hideOtherRecordsInPopup: true,
componentType: 'FormItem',
createBlockSchema: createEditFormBlock,
templateWrap: templateWrap,
templateWrap: templateWrapEdit,
showAssociationFields: true,
};
},
@ -133,6 +148,7 @@ function useRecordBlocks() {
dataSource: collection.dataSource,
useComponentProps() {
const { createAssociationFormBlock, templateWrap } = useCreateAssociationFormBlock();
const { createFormBlock, templateWrap: templateWrapCollection } = useCreateFormBlock();
return {
filterCollections({ collection, associationField }) {
if (associationField) {
@ -143,38 +159,29 @@ function useRecordBlocks() {
onlyCurrentDataSource: true,
hideSearch: true,
componentType: 'FormItem',
createBlockSchema: createAssociationFormBlock,
templateWrap: templateWrap,
createBlockSchema: ({ item, fromOthersInPopup }) => {
if (fromOthersInPopup) {
return createFormBlock({ item });
}
createAssociationFormBlock({ item });
},
templateWrap: (templateSchema, { item, fromOthersInPopup }) => {
if (fromOthersInPopup) {
return templateWrapCollection(templateSchema, { item });
}
templateWrap(templateSchema, { item });
},
showAssociationFields: true,
};
},
useVisible() {
const collection = useCollection();
return useMemo(
() =>
collection.fields.some(
(field) => canMakeAssociationBlock(field) && ['hasMany', 'belongsToMany'].includes(field.type),
),
[collection.fields],
);
},
},
{
name: 'table',
title: '{{t("Table")}}',
Component: 'TableBlockInitializer',
useVisible() {
const collection = useCollection();
return useMemo(
() =>
collection.fields.some(
(field) => canMakeAssociationBlock(field) && ['hasMany', 'belongsToMany'].includes(field.type),
),
[collection.fields],
);
},
useComponentProps() {
const { createAssociationTableBlock } = useCreateAssociationTableBlock();
const { createTableBlock } = useCreateTableBlock();
return {
hideSearch: true,
@ -185,7 +192,12 @@ function useRecordBlocks() {
}
return false;
},
createBlockSchema: createAssociationTableBlock,
createBlockSchema: ({ item, fromOthersInPopup }) => {
if (fromOthersInPopup) {
return createTableBlock({ item });
}
createAssociationTableBlock({ item });
},
showAssociationFields: true,
};
},
@ -194,18 +206,9 @@ function useRecordBlocks() {
name: 'list',
title: '{{t("List")}}',
Component: 'ListBlockInitializer',
useVisible() {
const collection = useCollection();
return useMemo(
() =>
collection.fields.some(
(field) => canMakeAssociationBlock(field) && ['hasMany', 'belongsToMany'].includes(field.type),
),
[collection.fields],
);
},
useComponentProps() {
const { createAssociationListBlock } = useCreateAssociationListBlock();
const { createListBlock } = useCreateListBlock();
return {
hideSearch: true,
@ -216,7 +219,12 @@ function useRecordBlocks() {
}
return false;
},
createBlockSchema: createAssociationListBlock,
createBlockSchema: ({ item, fromOthersInPopup }) => {
if (fromOthersInPopup) {
return createListBlock({ item });
}
createAssociationListBlock({ item });
},
showAssociationFields: true,
};
},
@ -225,18 +233,9 @@ function useRecordBlocks() {
name: 'gridCard',
title: '{{t("Grid Card")}}',
Component: 'GridCardBlockInitializer',
useVisible() {
const collection = useCollection();
return useMemo(
() =>
collection.fields.some(
(field) => canMakeAssociationBlock(field) && ['hasMany', 'belongsToMany'].includes(field.type),
),
[collection.fields],
);
},
useComponentProps() {
const { createAssociationGridCardBlock } = useCreateAssociationGridCardBlock();
const { createGridCardBlock } = useCreateGridCardBlock();
return {
hideSearch: true,
@ -247,7 +246,12 @@ function useRecordBlocks() {
}
return false;
},
createBlockSchema: createAssociationGridCardBlock,
createBlockSchema: ({ item, fromOthersInPopup }) => {
if (fromOthersInPopup) {
return createGridCardBlock({ item });
}
createAssociationGridCardBlock({ item });
},
showAssociationFields: true,
};
},

View File

@ -8,11 +8,11 @@ import {
useGetSchemaInitializerMenuItems,
useSchemaInitializer,
} from '../../application';
import { DataSource } from '../../data-source';
import { Collection, CollectionFieldOptions } from '../../data-source/collection/Collection';
import { useCompile } from '../../schema-component';
import { useSchemaTemplateManager } from '../../schema-templates';
import { useCollectionDataSourceItems } from '../utils';
import { DataSource } from '../../data-source';
const MENU_ITEM_HEIGHT = 40;
const STEP = 15;
@ -260,8 +260,10 @@ export interface DataBlockInitializerProps {
templateSchema: any,
{
item,
fromOthersInPopup,
}: {
item: any;
fromOthersInPopup?: boolean;
},
) => any;
onCreateBlockSchema?: (args: any) => void;
@ -278,6 +280,14 @@ export interface DataBlockInitializerProps {
/** 如果只有一项数据表时,不显示 children 列表 */
hideChildrenIfSingleCollection?: boolean;
items?: ReturnType<typeof useCollectionDataSourceItems>[];
/**
* Others
*/
fromOthersInPopup?: boolean;
/**
* Other records
*/
hideOtherRecordsInPopup?: boolean;
}
export const DataBlockInitializer = (props: DataBlockInitializerProps) => {
@ -295,6 +305,8 @@ export const DataBlockInitializer = (props: DataBlockInitializerProps) => {
hideChildrenIfSingleCollection,
filterDataSource,
items: itemsFromProps,
fromOthersInPopup,
hideOtherRecordsInPopup,
} = props;
const { insert, setVisible } = useSchemaInitializer();
const compile = useCompile();
@ -303,15 +315,15 @@ export const DataBlockInitializer = (props: DataBlockInitializerProps) => {
async ({ item }) => {
if (item.template) {
const s = await getTemplateSchemaByMode(item);
templateWrap ? insert(templateWrap(s, { item })) : insert(s);
templateWrap ? insert(templateWrap(s, { item, fromOthersInPopup })) : insert(s);
} else {
if (onCreateBlockSchema) {
onCreateBlockSchema({ item });
onCreateBlockSchema({ item, fromOthersInPopup });
}
}
setVisible(false);
},
[getTemplateSchemaByMode, insert, onCreateBlockSchema, setVisible, templateWrap],
[fromOthersInPopup, getTemplateSchemaByMode, insert, onCreateBlockSchema, setVisible, templateWrap],
);
const items =
itemsFromProps ||
@ -323,6 +335,7 @@ export const DataBlockInitializer = (props: DataBlockInitializerProps) => {
onlyCurrentDataSource,
showAssociationFields,
dataBlockInitializerProps: props,
hideOtherRecordsInPopup,
});
const getMenuItems = useGetSchemaInitializerMenuItems(onClick);
const childItems = useMemo(() => {

View File

@ -1,11 +1,11 @@
import { TableOutlined } from '@ant-design/icons';
import React, { useCallback } from 'react';
import { useCollectionManager_deprecated } from '../../collection-manager';
import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../application';
import { useCollectionManager_deprecated } from '../../collection-manager';
import { createGridCardBlockUISchema } from '../../modules/blocks/data-blocks/grid-card/createGridCardBlockUISchema';
import { useSchemaTemplateManager } from '../../schema-templates';
import { useRecordCollectionDataSourceItems } from '../utils';
import { createGridCardBlockSchema } from '../../modules/blocks/data-blocks/grid-card/createGridCardBlockSchema';
/**
* @deprecated
@ -30,7 +30,7 @@ export const RecordAssociationGridCardBlockInitializer = () => {
insert(s);
} else {
insert(
createGridCardBlockSchema({
createGridCardBlockUISchema({
rowKey: collection.filterTargetKey,
dataSource: collection.dataSource,
association: resource,
@ -53,7 +53,7 @@ export function useCreateAssociationGridCardBlock() {
const collection = getCollection(field.target);
insert(
createGridCardBlockSchema({
createGridCardBlockUISchema({
rowKey: collection.filterTargetKey,
dataSource: collection.dataSource,
association: `${field.collectionName}.${field.name}`,

View File

@ -3,9 +3,9 @@ import React, { useCallback } from 'react';
import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../application';
import { useCollectionManager_deprecated } from '../../collection-manager';
import { createListBlockUISchema } from '../../modules/blocks/data-blocks/list/createListBlockUISchema';
import { useSchemaTemplateManager } from '../../schema-templates';
import { useRecordCollectionDataSourceItems } from '../utils';
import { createListBlockSchema } from '../../modules/blocks/data-blocks/list/createListBlockSchema';
export const RecordAssociationListBlockInitializer = () => {
const itemConfig = useSchemaInitializerItem();
@ -27,7 +27,7 @@ export const RecordAssociationListBlockInitializer = () => {
insert(s);
} else {
insert(
createListBlockSchema({
createListBlockUISchema({
rowKey: collection.filterTargetKey,
dataSource: collection.dataSource,
association: resource,
@ -50,7 +50,7 @@ export function useCreateAssociationListBlock() {
const collection = getCollection(field.target);
insert(
createListBlockSchema({
createListBlockUISchema({
rowKey: collection.filterTargetKey,
dataSource: collection.dataSource,
association: `${field.collectionName}.${field.name}`,

View File

@ -842,6 +842,7 @@ export const useCollectionDataSourceItems = ({
showAssociationFields,
filterDataSource,
dataBlockInitializerProps,
hideOtherRecordsInPopup,
}: {
componentName;
filter?: (options: { collection?: Collection; associationField?: CollectionFieldOptions }) => boolean;
@ -849,6 +850,10 @@ export const useCollectionDataSourceItems = ({
showAssociationFields?: boolean;
filterDataSource?: (dataSource?: DataSource) => boolean;
dataBlockInitializerProps?: any;
/**
* Other records
*/
hideOtherRecordsInPopup?: boolean;
}) => {
const { t } = useTranslation();
const dm = useDataSourceManager();
@ -936,19 +941,56 @@ export const useCollectionDataSourceItems = ({
],
},
};
const componentTypeMap = {
ReadPrettyFormItem: 'Details',
};
const otherRecords = {
name: 'otherRecords',
Component: DataBlockInitializer,
// 目的是使点击无效
onClick() {},
componentProps: {
icon: null,
title: t('Other records'),
name: 'otherRecords',
showAssociationFields: false,
onlyCurrentDataSource: false,
hideChildrenIfSingleCollection: false,
onCreateBlockSchema: dataBlockInitializerProps.onCreateBlockSchema,
fromOthersInPopup: true,
componentType: componentTypeMap[componentName] || componentName,
filter({ collection: c, associationField }) {
return true;
},
},
};
let children;
const _associationRecords = associationFields.length ? associationRecords : null;
if (noAssociationMenu[0].children.length && associationFields.length) {
children = [currentRecord, associationRecords];
} else if (noAssociationMenu[0].children.length) {
// 当可选数据表只有一个时,实现只点击一次区块 menu 就能创建区块
if (noAssociationMenu[0].children.length <= 1) {
noAssociationMenu[0].children = (noAssociationMenu[0].children[0]?.children as any) || [];
return noAssociationMenu;
if (hideOtherRecordsInPopup) {
children = [currentRecord, _associationRecords];
} else {
children = [currentRecord, _associationRecords, otherRecords];
}
} else if (noAssociationMenu[0].children.length) {
if (hideOtherRecordsInPopup) {
// 当可选数据表只有一个时,实现只点击一次区块 menu 就能创建区块
if (noAssociationMenu[0].children.length <= 1) {
noAssociationMenu[0].children = (noAssociationMenu[0].children[0]?.children as any) || [];
return noAssociationMenu;
}
children = [currentRecord];
} else {
children = [currentRecord, otherRecords];
}
children = [currentRecord];
} else {
children = [associationRecords];
if (hideOtherRecordsInPopup) {
children = [_associationRecords];
} else {
children = [_associationRecords, otherRecords];
}
}
return [
@ -956,10 +998,19 @@ export const useCollectionDataSourceItems = ({
name: 'records',
label: t('Records'),
type: 'subMenu',
children,
children: children.filter(Boolean),
},
];
}, [associationFields, collection.dataSource, collection.name, dataBlockInitializerProps, noAssociationMenu, t]);
}, [
associationFields,
collection.dataSource,
collection.name,
componentName,
dataBlockInitializerProps,
hideOtherRecordsInPopup,
noAssociationMenu,
t,
]);
}
return noAssociationMenu;

View File

@ -1,4 +1,4 @@
import { test, expect } from '@nocobase/test/e2e';
import { expect, test } from '@nocobase/test/e2e';
import { emptyPageWithCalendarCollection, oneTableWithCalendarCollection } from './templates';
test.describe('where can be added', () => {
@ -18,7 +18,7 @@ test.describe('where can be added', () => {
test('association block in popup', async ({ page, mockPage, mockRecord }) => {
await mockPage(oneTableWithCalendarCollection).goto();
await mockRecord('toManyCalendar');
const record = await mockRecord('toManyCalendar');
// 打开弹窗
await page.getByLabel('action-Action.Link-View-view-').first().click();
@ -32,5 +32,16 @@ test.describe('where can be added', () => {
await page.getByRole('button', { name: 'OK', exact: true }).click();
await expect(page.getByLabel('block-item-CardItem-calendar-').getByText('Sun', { exact: true })).toBeVisible();
// 通过 Other records 创建一个日历区块
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'form Calendar right' }).hover();
await page.getByRole('menuitem', { name: 'Other records right' }).hover();
await page.getByRole('menuitem', { name: 'calendar', exact: true }).click();
await page.mouse.move(300, 0);
await page.getByLabel('block-item-Select-Title field').getByTestId('select-single').click();
await page.getByRole('option', { name: 'title' }).click();
await page.getByRole('button', { name: 'OK', exact: true }).click();
await expect(page.getByText(record.manyToMany[0].title).first()).toBeVisible();
});
});

View File

@ -1,8 +1,9 @@
import { Plugin, canMakeAssociationBlock, useCollection } from '@nocobase/client';
import { Plugin } from '@nocobase/client';
import { generateNTemplate } from '../locale';
import { CalendarV2 } from './calendar';
import { calendarBlockSettings } from './calendar/Calender.Settings';
import { CalendarCollectionTemplate } from './collection-templates/calendar';
import { useCalendarBlockDecoratorProps } from './hooks/useCalendarBlockDecoratorProps';
import { CalendarBlockProvider, useCalendarBlockProps } from './schema-initializer/CalendarBlockProvider';
import {
CalendarActionInitializers_deprecated,
@ -14,9 +15,8 @@ import {
CalendarBlockInitializer,
RecordAssociationCalendarBlockInitializer,
useCreateAssociationCalendarBlock,
useCreateCalendarBlock,
} from './schema-initializer/items';
import { useMemo } from 'react';
import { useCalendarBlockDecoratorProps } from './hooks/useCalendarBlockDecoratorProps';
export class PluginCalendarClient extends Plugin {
async load() {
@ -28,18 +28,9 @@ export class PluginCalendarClient extends Plugin {
this.app.schemaInitializerManager.addItem('popup:common:addBlock', 'dataBlocks.calendar', {
title: generateNTemplate('Calendar'),
Component: 'CalendarBlockInitializer',
useVisible() {
const collection = useCollection();
return useMemo(
() =>
collection.fields.some(
(field) => canMakeAssociationBlock(field) && ['hasMany', 'belongsToMany'].includes(field.type),
),
[collection.fields],
);
},
useComponentProps() {
const { createAssociationCalendarBlock } = useCreateAssociationCalendarBlock();
const { createCalendarBlock } = useCreateCalendarBlock();
return {
onlyCurrentDataSource: true,
@ -49,7 +40,12 @@ export class PluginCalendarClient extends Plugin {
}
return false;
},
createBlockSchema: createAssociationCalendarBlock,
createBlockSchema: ({ item, fromOthersInPopup }) => {
if (fromOthersInPopup) {
return createCalendarBlock({ item });
}
createAssociationCalendarBlock({ item });
},
showAssociationFields: true,
hideSearch: true,
};

View File

@ -14,8 +14,8 @@ import {
useSchemaInitializerItem,
} from '@nocobase/client';
import React, { useContext } from 'react';
import { createCalendarBlockUISchema } from '../createCalendarBlockUISchema';
import { useTranslation } from '../../../locale';
import { createCalendarBlockUISchema } from '../createCalendarBlockUISchema';
export const CalendarBlockInitializer = ({
filterCollections,
@ -30,79 +30,19 @@ export const CalendarBlockInitializer = ({
createBlockSchema?: (options: any) => any;
showAssociationFields?: boolean;
}) => {
const { insert } = useSchemaInitializer();
const { t } = useTranslation();
const { getCollectionField, getCollectionFieldsOptions } = useCollectionManager_deprecated();
const options = useContext(SchemaOptionsContext);
const { theme } = useGlobalTheme();
const itemConfig = useSchemaInitializerItem();
const { createCalendarBlock } = useCreateCalendarBlock();
return (
<DataBlockInitializer
{...itemConfig}
componentType={'Calendar'}
icon={<FormOutlined />}
onCreateBlockSchema={async ({ item }) => {
onCreateBlockSchema={async (options) => {
if (createBlockSchema) {
return createBlockSchema({ item });
return createBlockSchema(options);
}
const stringFieldsOptions = getCollectionFieldsOptions(item.name, 'string', { dataSource: item.dataSource });
const dateFieldsOptions = getCollectionFieldsOptions(item.name, 'date', {
association: ['o2o', 'obo', 'oho', 'm2o'],
dataSource: item.dataSource,
});
const values = await FormDialog(
t('Create calendar block'),
() => {
return (
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
<FormLayout layout={'vertical'}>
<SchemaComponent
schema={{
properties: {
title: {
title: t('Title field'),
enum: stringFieldsOptions,
required: true,
'x-component': 'Select',
'x-decorator': 'FormItem',
},
start: {
title: t('Start date field'),
enum: dateFieldsOptions,
required: true,
default: getCollectionField(`${item.name}.createdAt`) ? 'createdAt' : null,
'x-component': 'Cascader',
'x-decorator': 'FormItem',
},
end: {
title: t('End date field'),
enum: dateFieldsOptions,
'x-component': 'Cascader',
'x-decorator': 'FormItem',
},
},
}}
/>
</FormLayout>
</SchemaComponentOptions>
);
},
theme,
).open({
initialValues: {},
});
insert(
createCalendarBlockUISchema({
collectionName: item.name,
dataSource: item.dataSource,
fieldNames: {
...values,
},
}),
);
createCalendarBlock(options);
}}
onlyCurrentDataSource={onlyCurrentDataSource}
hideSearch={hideSearch}
@ -111,3 +51,72 @@ export const CalendarBlockInitializer = ({
/>
);
};
export const useCreateCalendarBlock = () => {
const { insert } = useSchemaInitializer();
const { t } = useTranslation();
const { getCollectionField, getCollectionFieldsOptions } = useCollectionManager_deprecated();
const options = useContext(SchemaOptionsContext);
const { theme } = useGlobalTheme();
const createCalendarBlock = async ({ item }) => {
const stringFieldsOptions = getCollectionFieldsOptions(item.name, 'string', { dataSource: item.dataSource });
const dateFieldsOptions = getCollectionFieldsOptions(item.name, 'date', {
association: ['o2o', 'obo', 'oho', 'm2o'],
dataSource: item.dataSource,
});
const values = await FormDialog(
t('Create calendar block'),
() => {
return (
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
<FormLayout layout={'vertical'}>
<SchemaComponent
schema={{
properties: {
title: {
title: t('Title field'),
enum: stringFieldsOptions,
required: true,
'x-component': 'Select',
'x-decorator': 'FormItem',
},
start: {
title: t('Start date field'),
enum: dateFieldsOptions,
required: true,
default: getCollectionField(`${item.name}.createdAt`) ? 'createdAt' : null,
'x-component': 'Cascader',
'x-decorator': 'FormItem',
},
end: {
title: t('End date field'),
enum: dateFieldsOptions,
'x-component': 'Cascader',
'x-decorator': 'FormItem',
},
},
}}
/>
</FormLayout>
</SchemaComponentOptions>
);
},
theme,
).open({
initialValues: {},
});
insert(
createCalendarBlockUISchema({
collectionName: item.name,
dataSource: item.dataSource,
fieldNames: {
...values,
},
}),
);
};
return { createCalendarBlock };
};