feat: support to add Settings block in mobile (#5025)

* feat: support to add Settings block in mobile

* style(Popup): clear the margin-bottom of the last block

* refactor: extract hideDivider

* fix: fix form values
This commit is contained in:
Zeke Zhang 2024-08-09 11:45:22 +08:00 committed by GitHub
parent 5f9f94d008
commit 4590c754ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 190 additions and 36 deletions

View File

@ -24,8 +24,7 @@ export const useMobileActionDrawerStyle = createStyles(({ css, token }: any) =>
// to match the button named 'Add block'
& + .nb-grid-container > .nb-grid > .nb-grid-warp > .ant-btn {
// 18px is the token marginBlock value
margin: 12px 12px calc(12px + 18px);
margin: 12px;
}
`,
@ -48,6 +47,15 @@ export const useMobileActionDrawerStyle = createStyles(({ css, token }: any) =>
overflow-y: auto;
overflow-x: hidden;
background-color: ${token.colorBgLayout};
// clear the margin-bottom of the last block
& > .nb-grid-container > .nb-grid > .nb-grid-warp > .nb-grid-row:nth-last-child(2) .noco-card-item {
margin-bottom: 0;
& > .ant-card {
margin-bottom: 0 !important;
}
}
`,
footer: css`
@ -62,6 +70,10 @@ export const useMobileActionDrawerStyle = createStyles(({ css, token }: any) =>
z-index: 1000;
border-top: 1px solid ${token.colorSplit};
background-color: ${token.colorBgLayout};
.ant-btn {
margin-left: 8px;
}
`,
};
});

View File

@ -7,7 +7,7 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { observer, RecursionField, useField, useFieldSchema } from '@formily/react';
import { ISchema, observer, RecursionField, useField, useFieldSchema } from '@formily/react';
import { Action, SchemaComponent, useActionContext } from '@nocobase/client';
import { ConfigProvider } from 'antd';
import { Popup } from 'antd-mobile';
@ -17,7 +17,7 @@ import { useMobileActionDrawerStyle } from './ActionDrawer.style';
import { BasicZIndexProvider, MIN_Z_INDEX_INCREMENT, useBasicZIndex } from './BasicZIndexProvider';
import { usePopupContainer } from './FilterAction';
export const ActionDrawerUsedInMobile = observer((props: { footerNodeName?: string }) => {
export const ActionDrawerUsedInMobile: any = observer((props: { footerNodeName?: string }) => {
const fieldSchema = useFieldSchema();
const field = useField();
const { visible, setVisible } = useActionContext();
@ -25,6 +25,13 @@ export const ActionDrawerUsedInMobile = observer((props: { footerNodeName?: stri
const { styles } = useMobileActionDrawerStyle();
const { basicZIndex } = useBasicZIndex();
// this schema need to add padding in the content area of the popup
const isSpecialSchema = isChangePasswordSchema(fieldSchema) || isEditProfileSchema(fieldSchema);
const footerNodeName = isSpecialSchema ? 'Action.Drawer.Footer' : props.footerNodeName;
const specialStyle = isSpecialSchema ? { backgroundColor: 'white' } : {};
const newZIndex = basicZIndex + MIN_Z_INDEX_INCREMENT;
const zIndexStyle = useMemo(() => {
@ -34,7 +41,7 @@ export const ActionDrawerUsedInMobile = observer((props: { footerNodeName?: stri
}, [newZIndex]);
const footerSchema = fieldSchema.reduceProperties((buf, s) => {
if (s['x-component'] === props.footerNodeName) {
if (s['x-component'] === footerNodeName) {
return s;
}
return buf;
@ -81,24 +88,32 @@ export const ActionDrawerUsedInMobile = observer((props: { footerNodeName?: stri
<CloseOutline />
</span>
</div>
<SchemaComponent
schema={fieldSchema}
onlyRenderProperties
filterProperties={(s) => {
return s['x-component'] !== props.footerNodeName;
}}
/>
{/* used to offset the margin-bottom of the last block */}
{/* The number 1 is to prevent the scroll bar from appearing */}
<div style={{ marginBottom: 1 - marginBlock }}></div>
{isSpecialSchema ? (
<div style={{ padding: 12, ...specialStyle }}>
<SchemaComponent
schema={fieldSchema}
filterProperties={(s) => {
return s['x-component'] !== footerNodeName;
}}
/>
</div>
) : (
<SchemaComponent
schema={fieldSchema}
onlyRenderProperties
filterProperties={(s) => {
return s['x-component'] !== footerNodeName;
}}
/>
)}
{footerSchema ? (
<div className={styles.footer}>
<div className={styles.footer} style={isSpecialSchema ? specialStyle : null}>
<RecursionField
basePath={field.address}
schema={fieldSchema}
onlyRenderProperties
filterProperties={(s) => {
return s['x-component'] === props.footerNodeName;
return s['x-component'] === footerNodeName;
}}
/>
</div>
@ -125,3 +140,11 @@ export const useToAdaptActionDrawerToMobile = () => {
};
}, []);
};
function isEditProfileSchema(schema: ISchema) {
return schema.title === `{{t("Edit profile")}}`;
}
function isChangePasswordSchema(schema: ISchema) {
return schema.title === `{{t("Change password")}}`;
}

View File

@ -108,7 +108,7 @@ export const useToAdaptFilterActionToMobile = () => {
};
/**
* 使 mobile-container https://nocobase.height.app/T-4959
* mobile-container transformhttps://nocobase.height.app/T-4959
* @param visible
* @returns
*/

View File

@ -0,0 +1,24 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { Schema } from '@nocobase/utils';
// 隐藏 Grid 组件的左右 divider因为移动端不需要在一行中并列展示两个区块
export function hideDivider(schema: Schema) {
schema?.mapProperties((schema) => {
if (schema['x-component'] === 'Grid') {
schema['x-component-props'] = {
...schema['x-component-props'],
showDivider: false,
};
}
});
return schema;
}

View File

@ -7,7 +7,7 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { observer, RecursionField, Schema, useField, useFieldSchema } from '@formily/react';
import { observer, RecursionField, useField, useFieldSchema } from '@formily/react';
import {
css,
DndContext,
@ -27,6 +27,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { MobilePageHeader } from '../../pages/dynamic-page';
import { MobilePageContentContainer } from '../../pages/dynamic-page/content/MobilePageContentContainer';
import { useStyles } from '../../pages/dynamic-page/header/tabs';
import { hideDivider } from '../hideDivider';
import { useMobileTabsForMobileActionPageStyle } from './MobileTabsForMobileActionPage.style';
export const MobileTabsForMobileActionPage: any = observer(
@ -162,17 +163,3 @@ MobileTabsForMobileActionPage.TabPane = observer(
);
MobileTabsForMobileActionPage.Designer = TabsOfPC.Designer;
// 隐藏 Grid 组件的左右 divider因为移动端不需要在一行中并列展示两个区块
function hideDivider(tabPaneSchema: Schema) {
tabPaneSchema?.mapProperties((schema) => {
if (schema['x-component'] === 'Grid') {
schema['x-component-props'] = {
...schema['x-component-props'],
showDivider: false,
};
}
});
return tabPaneSchema;
}

View File

@ -45,6 +45,9 @@ import {
// 导出 JSBridge会挂在到 window 上
import './js-bridge';
import { MobileSettings } from './mobile-blocks/settings-block/MobileSettings';
import { MobileSettingsBlockInitializer } from './mobile-blocks/settings-block/MobileSettingsBlockInitializer';
import { MobileSettingsBlockSchemaSettings } from './mobile-blocks/settings-block/schemaSettings';
import { MobileCheckerProvider } from './providers';
export * from './desktop-mode';
@ -135,6 +138,7 @@ export class PluginMobileClient extends Plugin {
mobileTabBarLinkSettings,
mobilePageTabsSettings,
mobileNavigationBarLinkSettings,
MobileSettingsBlockSchemaSettings,
);
}
@ -150,6 +154,8 @@ export class PluginMobileClient extends Plugin {
MobilePageTabs,
MobileNavigationActionBar,
MobileNotFoundPage,
MobileSettingsBlockInitializer,
MobileSettings,
});
}

View File

@ -0,0 +1,29 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { cx, SettingsMenu, SortableItem, useDesigner, useToken } from '@nocobase/client';
import React, { useMemo } from 'react';
export const InternalSettings = () => {
const Designer = useDesigner();
const { token } = useToken();
const style = useMemo(() => {
return {
marginBottom: token.marginBlock,
};
}, [token.marginBlock]);
return (
<SortableItem className={cx('nb-mobile-setting')} style={style}>
<Designer />
<SettingsMenu />
</SortableItem>
);
};
export const MobileSettings = InternalSettings;

View File

@ -0,0 +1,31 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { SettingOutlined } from '@ant-design/icons';
import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '@nocobase/client';
import React from 'react';
export const MobileSettingsBlockInitializer = () => {
const itemConfig = useSchemaInitializerItem();
const { insert } = useSchemaInitializer();
return (
<SchemaInitializerItem
icon={<SettingOutlined />}
onClick={async () => {
insert({
type: 'void',
'x-component': 'MobileSettings',
'x-settings': 'blockSettings:mobileSettings',
'x-component-props': {},
});
}}
{...itemConfig}
/>
);
};

View File

@ -0,0 +1,34 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { SchemaSettings } from '@nocobase/client';
import { usePluginTranslation } from '../../locale';
export const MobileSettingsBlockSchemaSettings = new SchemaSettings({
name: 'blockSettings:mobileSettings',
items: [
{
name: 'delete',
type: 'remove',
useComponentProps() {
const { t } = usePluginTranslation();
return {
removeParentsIfNoChildren: true,
confirm: {
title: t('Delete settings block'),
},
breakRemoveOn: {
'x-component': 'Grid',
},
};
},
},
],
});

View File

@ -8,6 +8,7 @@
*/
import { SchemaInitializer, gridRowColWrap } from '@nocobase/client';
import { generatePluginTranslationTemplate } from '../../../locale';
export const mobileAddBlockInitializer = new SchemaInitializer({
title: '{{t("Add block")}}',
@ -60,6 +61,11 @@ export const mobileAddBlockInitializer = new SchemaInitializer({
title: '{{t("Markdown")}}',
Component: 'MarkdownBlockInitializer',
},
{
name: 'settings',
title: generatePluginTranslationTemplate('Settings'),
Component: 'MobileSettingsBlockInitializer',
},
],
},
],

View File

@ -20,5 +20,6 @@
"Title field is required": "Title field is required",
"Icon field is required": "Icon field is required",
"Desktop data blocks": "Desktop data blocks",
"Other desktop blocks": "Other desktop blocks"
"Other desktop blocks": "Other desktop blocks",
"Settings": "Settings"
}

View File

@ -21,5 +21,6 @@
"Title field is required": "标题必填",
"Icon field is required": "图标必填",
"Desktop data blocks": "桌面端数据区块",
"Other desktop blocks": "其他桌面端区块"
"Other desktop blocks": "其他桌面端区块",
"Settings": "设置"
}

View File

@ -7,7 +7,7 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { SelectWithTitle, useAPIClient, useCurrentUserContext, useSystemSettings } from '@nocobase/client';
import { SelectWithTitle, useCurrentUserContext, useSystemSettings } from '@nocobase/client';
import { error } from '@nocobase/utils/client';
import { MenuProps } from 'antd';
import React, { useEffect, useMemo } from 'react';