mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-01 18:52:20 +08:00
feat: adapt desktop blocks to mobile (#4945)
* feat: register workflow blocks to mobile page * fix: should hide Divider in subpage * refactor: rename 'Data blocks' to 'Desktop data blocks' * feat: adapt blocks within subpages for mobile * feat: adapt Filter action * feat: isolate block templates between desktop and mobile * refactor: export storePopupContext * feat: support popup URL for 'Workflow todos' * chore: update e2e tests * chore: make e2e tests pass * chore: add comment * fix: make popup style of duplicate and bulk edit right * fix(GridCard): ensure single column display in mobile * fix: fix goBack * refactor: make more stable * refactor: change name for add blocks menu * fix: fix block template for mobile * feat: adapt Apply action of approval block to mobile * fix(Map): use window.open to redirect to configuration page * Revert "fix(Map): use window.open to redirect to configuration page" This reverts commit 248ae8b68cfd78415184dfab2442081363872fb0. * fix: redirect to the main app page when URL is starts with 'admin' * fix(Link): make path right * fix: refactor Popup to fix draging bug * fix: should auto refresh when submiting in Manual popup * fix(Action.Container): should return null when visible is false (T-4949) * fix: increase z-index of subpage to cover Amap elements * fix: fix tab switching not work (T-4985) * fix(Link): should be change Link's URL of all table rows after editing URL (T-4981) * fix: fix URL not changed after closing popup (T-4987) * fix: make unit tests pass * fix: make unit tests pass * chore: get e2e tests to pass * fix: use Popup to display data picker (T-4965) * fix: use mobile Popup in some bloks * refactor: use local isMobile * fix: increase Popup's z-index to cover subpage * fix: optimize Popup for mobile * style: createRecordAction style improve * refactor(AssociationField): get Component from AssociationFieldModeProvider * refactor(InternalPopoverNester): support custom Container component * feat: adapt PopoverNester to mobile * chore: update unit tests * fix: get e2e tests to pass * chore: make e2e more stable * refactor: move mobile-action-page in adaptor-of-desktop folder * fix: get the z-index of popups and subpages correct * feat: unify the styles of popups * chore: make e2e more stable --------- Co-authored-by: chenos <chenlinxh@gmail.com> Co-authored-by: katherinehhh <katherine_15995@163.com>
This commit is contained in:
parent
64e53558df
commit
a429b7a4b3
@ -2,9 +2,7 @@
|
|||||||
"version": "1.3.0-alpha",
|
"version": "1.3.0-alpha",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
"npmClientArgs": [
|
"npmClientArgs": ["--ignore-engines"],
|
||||||
"--ignore-engines"
|
|
||||||
],
|
|
||||||
"command": {
|
"command": {
|
||||||
"version": {
|
"version": {
|
||||||
"forcePublish": true,
|
"forcePublish": true,
|
||||||
|
@ -20,7 +20,7 @@ import omit from 'lodash/omit';
|
|||||||
import qs from 'qs';
|
import qs from 'qs';
|
||||||
import { ChangeEvent, useCallback, useContext, useEffect, useMemo } from 'react';
|
import { ChangeEvent, useCallback, useContext, useEffect, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { NavigateFunction } from 'react-router-dom';
|
import { NavigateFunction, useHref } from 'react-router-dom';
|
||||||
import { useReactToPrint } from 'react-to-print';
|
import { useReactToPrint } from 'react-to-print';
|
||||||
import {
|
import {
|
||||||
AssociationFilter,
|
AssociationFilter,
|
||||||
@ -1592,6 +1592,9 @@ export function useLinkActionProps(componentProps?: any) {
|
|||||||
const openInNewWindow = fieldSchema?.['x-component-props']?.['openInNewWindow'];
|
const openInNewWindow = fieldSchema?.['x-component-props']?.['openInNewWindow'];
|
||||||
const { parseURLAndParams } = useParseURLAndParams();
|
const { parseURLAndParams } = useParseURLAndParams();
|
||||||
|
|
||||||
|
// see: https://stackoverflow.com/questions/50449423/accessing-basename-of-browserouter
|
||||||
|
const basenameOfCurrentRouter = useHref('/');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'default',
|
type: 'default',
|
||||||
async onClick() {
|
async onClick() {
|
||||||
@ -1605,7 +1608,7 @@ export function useLinkActionProps(componentProps?: any) {
|
|||||||
if (openInNewWindow) {
|
if (openInNewWindow) {
|
||||||
window.open(completeURL(link), '_blank');
|
window.open(completeURL(link), '_blank');
|
||||||
} else {
|
} else {
|
||||||
navigateWithinSelf(link, navigate);
|
navigateWithinSelf(link, navigate, window.location.origin + basenameOfCurrentRouter);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error('link should be a string');
|
console.error('link should be a string');
|
||||||
@ -1719,18 +1722,18 @@ export function completeURL(url: string, origin = window.location.origin) {
|
|||||||
return url.startsWith('/') ? `${origin}${url}` : `${origin}/${url}`;
|
return url.startsWith('/') ? `${origin}${url}` : `${origin}/${url}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function navigateWithinSelf(link: string, navigate: NavigateFunction, origin = window.location.origin) {
|
export function navigateWithinSelf(link: string, navigate: NavigateFunction, basePath = window.location.origin) {
|
||||||
if (!_.isString(link)) {
|
if (!_.isString(link)) {
|
||||||
return console.error('link should be a string');
|
return console.error('link should be a string');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isURL(link)) {
|
if (isURL(link)) {
|
||||||
if (link.startsWith(origin)) {
|
if (link.startsWith(basePath)) {
|
||||||
navigate(link.replace(origin, ''));
|
navigate(completeURL(link.replace(basePath, ''), ''));
|
||||||
} else {
|
} else {
|
||||||
window.open(link, '_self');
|
window.open(link, '_self');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
navigate(link.startsWith('/') ? link : `/${link}`);
|
navigate(completeURL(link, ''));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
import { useFieldSchema } from '@formily/react';
|
import { useFieldSchema } from '@formily/react';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useBlockTemplateContext } from '../../schema-templates/BlockTemplate';
|
import { useBlockTemplateContext } from '../../schema-templates/BlockTemplateProvider';
|
||||||
|
|
||||||
export const useBlockHeightProps = () => {
|
export const useBlockHeightProps = () => {
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
@ -26,11 +26,21 @@ const AppInner = memo(({ children }: { children: React.ReactNode }) => {
|
|||||||
});
|
});
|
||||||
AppInner.displayName = 'AppInner';
|
AppInner.displayName = 'AppInner';
|
||||||
|
|
||||||
const AntdAppProvider = ({ children }: { children: React.ReactNode }) => {
|
const AntdAppProvider = ({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
style,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<App
|
<App
|
||||||
|
className={className}
|
||||||
style={{
|
style={{
|
||||||
height: '100%',
|
height: '100%',
|
||||||
|
...style,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AppInner>{children}</AppInner>
|
<AppInner>{children}</AppInner>
|
||||||
|
@ -44,7 +44,7 @@ const commonOptions = {
|
|||||||
showAssociationFields: true,
|
showAssociationFields: true,
|
||||||
onlyCurrentDataSource: true,
|
onlyCurrentDataSource: true,
|
||||||
hideSearch: true,
|
hideSearch: true,
|
||||||
componentType: 'FormItem',
|
componentType: `FormItem`,
|
||||||
currentText: t('Current collection'),
|
currentText: t('Current collection'),
|
||||||
otherText: t('Other collections'),
|
otherText: t('Other collections'),
|
||||||
},
|
},
|
||||||
|
@ -29,10 +29,11 @@ export const SchemaSettingsActionLinkItem: FC<SchemaSettingsActionLinkItemProps>
|
|||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { urlSchema, paramsSchema, openInNewWindowSchema } = useURLAndHTMLSchema();
|
const { urlSchema, paramsSchema, openInNewWindowSchema } = useURLAndHTMLSchema();
|
||||||
|
const componentProps = fieldSchema['x-component-props'] || {};
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
url: field.componentProps.url,
|
url: componentProps.url,
|
||||||
params: field.componentProps.params || [{}],
|
params: componentProps.params || [{}],
|
||||||
openInNewWindow: field.componentProps.openInNewWindow,
|
openInNewWindow: componentProps.openInNewWindow,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -52,7 +53,6 @@ export const SchemaSettingsActionLinkItem: FC<SchemaSettingsActionLinkItemProps>
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
onSubmit={({ url, params, openInNewWindow }) => {
|
onSubmit={({ url, params, openInNewWindow }) => {
|
||||||
const componentProps = fieldSchema['x-component-props'] || {};
|
|
||||||
componentProps.url = url;
|
componentProps.url = url;
|
||||||
componentProps.params = params;
|
componentProps.params = params;
|
||||||
componentProps.openInNewWindow = openInNewWindow;
|
componentProps.openInNewWindow = openInNewWindow;
|
||||||
|
@ -21,6 +21,7 @@ import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/Schem
|
|||||||
import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem';
|
import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem';
|
||||||
import { SchemaSettingsDataScope } from '../../../../schema-settings/SchemaSettingsDataScope';
|
import { SchemaSettingsDataScope } from '../../../../schema-settings/SchemaSettingsDataScope';
|
||||||
import { SchemaSettingsTemplate } from '../../../../schema-settings/SchemaSettingsTemplate';
|
import { SchemaSettingsTemplate } from '../../../../schema-settings/SchemaSettingsTemplate';
|
||||||
|
import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider';
|
||||||
import { setDataLoadingModeSettingsItem } from './setDataLoadingModeSettingsItem';
|
import { setDataLoadingModeSettingsItem } from './setDataLoadingModeSettingsItem';
|
||||||
|
|
||||||
const commonItems: SchemaSettingsItemType[] = [
|
const commonItems: SchemaSettingsItemType[] = [
|
||||||
@ -201,10 +202,11 @@ const commonItems: SchemaSettingsItemType[] = [
|
|||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { name } = useCollection_deprecated();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { componentNamePrefix } = useBlockTemplateContext();
|
||||||
const defaultResource =
|
const defaultResource =
|
||||||
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
return {
|
return {
|
||||||
componentName: 'Details',
|
componentName: `${componentNamePrefix}Details`,
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
resourceName: defaultResource,
|
resourceName: defaultResource,
|
||||||
};
|
};
|
||||||
|
@ -10,10 +10,11 @@
|
|||||||
import { useFieldSchema } from '@formily/react';
|
import { useFieldSchema } from '@formily/react';
|
||||||
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
||||||
import { SchemaSettingsItemType } from '../../../../application/schema-settings/types';
|
import { SchemaSettingsItemType } from '../../../../application/schema-settings/types';
|
||||||
import { useCollection_deprecated } from '../../../../collection-manager';
|
import { useCollection } from '../../../../data-source/collection/CollectionProvider';
|
||||||
import { SchemaSettingsFormItemTemplate, SchemaSettingsLinkageRules } from '../../../../schema-settings';
|
import { SchemaSettingsFormItemTemplate, SchemaSettingsLinkageRules } from '../../../../schema-settings';
|
||||||
import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem';
|
|
||||||
import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem';
|
import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem';
|
||||||
|
import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem';
|
||||||
|
import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider';
|
||||||
const commonItems: SchemaSettingsItemType[] = [
|
const commonItems: SchemaSettingsItemType[] = [
|
||||||
{
|
{
|
||||||
name: 'title',
|
name: 'title',
|
||||||
@ -27,7 +28,7 @@ const commonItems: SchemaSettingsItemType[] = [
|
|||||||
name: 'linkageRules',
|
name: 'linkageRules',
|
||||||
Component: SchemaSettingsLinkageRules,
|
Component: SchemaSettingsLinkageRules,
|
||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { name } = useCollection();
|
||||||
return {
|
return {
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
readPretty: true,
|
readPretty: true,
|
||||||
@ -38,13 +39,14 @@ const commonItems: SchemaSettingsItemType[] = [
|
|||||||
name: 'formItemTemplate',
|
name: 'formItemTemplate',
|
||||||
Component: SchemaSettingsFormItemTemplate,
|
Component: SchemaSettingsFormItemTemplate,
|
||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { name } = useCollection();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { componentNamePrefix } = useBlockTemplateContext();
|
||||||
const defaultResource =
|
const defaultResource =
|
||||||
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
return {
|
return {
|
||||||
insertAdjacentPosition: 'beforeEnd',
|
insertAdjacentPosition: 'beforeEnd',
|
||||||
componentName: 'ReadPrettyFormItem',
|
componentName: `${componentNamePrefix}ReadPrettyFormItem`,
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
resourceName: defaultResource,
|
resourceName: defaultResource,
|
||||||
};
|
};
|
||||||
|
@ -11,6 +11,7 @@ import { useFieldSchema } from '@formily/react';
|
|||||||
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
||||||
import { useFormBlockContext } from '../../../../block-provider/FormBlockProvider';
|
import { useFormBlockContext } from '../../../../block-provider/FormBlockProvider';
|
||||||
import { useCollection_deprecated } from '../../../../collection-manager';
|
import { useCollection_deprecated } from '../../../../collection-manager';
|
||||||
|
import { useCollection } from '../../../../data-source/collection/CollectionProvider';
|
||||||
import {
|
import {
|
||||||
SchemaSettingsDataTemplates,
|
SchemaSettingsDataTemplates,
|
||||||
SchemaSettingsFormItemTemplate,
|
SchemaSettingsFormItemTemplate,
|
||||||
@ -18,6 +19,7 @@ import {
|
|||||||
} from '../../../../schema-settings';
|
} from '../../../../schema-settings';
|
||||||
import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem';
|
import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem';
|
||||||
import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem';
|
import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem';
|
||||||
|
import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider';
|
||||||
|
|
||||||
export const createFormBlockSettings = new SchemaSettings({
|
export const createFormBlockSettings = new SchemaSettings({
|
||||||
name: 'blockSettings:createForm',
|
name: 'blockSettings:createForm',
|
||||||
@ -62,12 +64,13 @@ export const createFormBlockSettings = new SchemaSettings({
|
|||||||
name: 'formItemTemplate',
|
name: 'formItemTemplate',
|
||||||
Component: SchemaSettingsFormItemTemplate,
|
Component: SchemaSettingsFormItemTemplate,
|
||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { componentNamePrefix } = useBlockTemplateContext();
|
||||||
|
const { name } = useCollection();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const defaultResource =
|
const defaultResource =
|
||||||
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
return {
|
return {
|
||||||
componentName: 'FormItem',
|
componentName: `${componentNamePrefix}FormItem`,
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
resourceName: defaultResource,
|
resourceName: defaultResource,
|
||||||
};
|
};
|
||||||
|
@ -11,6 +11,7 @@ import { useFieldSchema } from '@formily/react';
|
|||||||
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
||||||
import { useFormBlockContext } from '../../../../block-provider/FormBlockProvider';
|
import { useFormBlockContext } from '../../../../block-provider/FormBlockProvider';
|
||||||
import { useCollection_deprecated } from '../../../../collection-manager';
|
import { useCollection_deprecated } from '../../../../collection-manager';
|
||||||
|
import { useCollection } from '../../../../data-source/collection/CollectionProvider';
|
||||||
import {
|
import {
|
||||||
SchemaSettingsDataTemplates,
|
SchemaSettingsDataTemplates,
|
||||||
SchemaSettingsFormItemTemplate,
|
SchemaSettingsFormItemTemplate,
|
||||||
@ -18,6 +19,7 @@ import {
|
|||||||
} from '../../../../schema-settings';
|
} from '../../../../schema-settings';
|
||||||
import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem';
|
import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem';
|
||||||
import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem';
|
import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem';
|
||||||
|
import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider';
|
||||||
|
|
||||||
export const editFormBlockSettings = new SchemaSettings({
|
export const editFormBlockSettings = new SchemaSettings({
|
||||||
name: 'blockSettings:editForm',
|
name: 'blockSettings:editForm',
|
||||||
@ -62,12 +64,13 @@ export const editFormBlockSettings = new SchemaSettings({
|
|||||||
name: 'formItemTemplate',
|
name: 'formItemTemplate',
|
||||||
Component: SchemaSettingsFormItemTemplate,
|
Component: SchemaSettingsFormItemTemplate,
|
||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { componentNamePrefix } = useBlockTemplateContext();
|
||||||
|
const { name } = useCollection();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const defaultResource =
|
const defaultResource =
|
||||||
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
return {
|
return {
|
||||||
componentName: 'FormItem',
|
componentName: `${componentNamePrefix}FormItem`,
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
resourceName: defaultResource,
|
resourceName: defaultResource,
|
||||||
};
|
};
|
||||||
|
@ -49,7 +49,7 @@ export const GridCardBlockInitializer = ({
|
|||||||
<DataBlockInitializer
|
<DataBlockInitializer
|
||||||
{...itemConfig}
|
{...itemConfig}
|
||||||
icon={<OrderedListOutlined />}
|
icon={<OrderedListOutlined />}
|
||||||
componentType={'GridCard'}
|
componentType={`GridCard`}
|
||||||
onCreateBlockSchema={async (options) => {
|
onCreateBlockSchema={async (options) => {
|
||||||
if (createBlockSchema) {
|
if (createBlockSchema) {
|
||||||
return createBlockSchema(options);
|
return createBlockSchema(options);
|
||||||
|
@ -20,9 +20,9 @@ import { pageSizeOptions } from '../../../../schema-component/antd/grid-card/opt
|
|||||||
import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem';
|
import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem';
|
||||||
import { SchemaSettingsDataScope } from '../../../../schema-settings/SchemaSettingsDataScope';
|
import { SchemaSettingsDataScope } from '../../../../schema-settings/SchemaSettingsDataScope';
|
||||||
import { SchemaSettingsTemplate } from '../../../../schema-settings/SchemaSettingsTemplate';
|
import { SchemaSettingsTemplate } from '../../../../schema-settings/SchemaSettingsTemplate';
|
||||||
|
import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider';
|
||||||
import { setDataLoadingModeSettingsItem } from '../details-multi/setDataLoadingModeSettingsItem';
|
import { setDataLoadingModeSettingsItem } from '../details-multi/setDataLoadingModeSettingsItem';
|
||||||
import { SetTheCountOfColumnsDisplayedInARow } from './SetTheCountOfColumnsDisplayedInARow';
|
import { SetTheCountOfColumnsDisplayedInARow } from './SetTheCountOfColumnsDisplayedInARow';
|
||||||
import { useCollection } from '../../../../data-source';
|
|
||||||
|
|
||||||
export const gridCardBlockSettings = new SchemaSettings({
|
export const gridCardBlockSettings = new SchemaSettings({
|
||||||
name: 'blockSettings:gridCard',
|
name: 'blockSettings:gridCard',
|
||||||
@ -210,11 +210,12 @@ export const gridCardBlockSettings = new SchemaSettings({
|
|||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { name } = useCollection_deprecated();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { componentNamePrefix } = useBlockTemplateContext();
|
||||||
const defaultResource =
|
const defaultResource =
|
||||||
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
componentName: 'GridCard',
|
componentName: `${componentNamePrefix}GridCard`,
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
resourceName: defaultResource,
|
resourceName: defaultResource,
|
||||||
};
|
};
|
||||||
|
@ -49,7 +49,7 @@ export const ListBlockInitializer = ({
|
|||||||
<DataBlockInitializer
|
<DataBlockInitializer
|
||||||
{...itemConfig}
|
{...itemConfig}
|
||||||
icon={<OrderedListOutlined />}
|
icon={<OrderedListOutlined />}
|
||||||
componentType={'List'}
|
componentType={`List`}
|
||||||
onCreateBlockSchema={async (options) => {
|
onCreateBlockSchema={async (options) => {
|
||||||
if (createBlockSchema) {
|
if (createBlockSchema) {
|
||||||
return createBlockSchema(options);
|
return createBlockSchema(options);
|
||||||
|
@ -19,8 +19,8 @@ import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/Schem
|
|||||||
import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem';
|
import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem';
|
||||||
import { SchemaSettingsDataScope } from '../../../../schema-settings/SchemaSettingsDataScope';
|
import { SchemaSettingsDataScope } from '../../../../schema-settings/SchemaSettingsDataScope';
|
||||||
import { SchemaSettingsTemplate } from '../../../../schema-settings/SchemaSettingsTemplate';
|
import { SchemaSettingsTemplate } from '../../../../schema-settings/SchemaSettingsTemplate';
|
||||||
|
import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider';
|
||||||
import { setDataLoadingModeSettingsItem } from '../details-multi/setDataLoadingModeSettingsItem';
|
import { setDataLoadingModeSettingsItem } from '../details-multi/setDataLoadingModeSettingsItem';
|
||||||
import { useCollection } from '../../../../data-source';
|
|
||||||
|
|
||||||
export const listBlockSettings = new SchemaSettings({
|
export const listBlockSettings = new SchemaSettings({
|
||||||
name: 'blockSettings:list',
|
name: 'blockSettings:list',
|
||||||
@ -212,11 +212,12 @@ export const listBlockSettings = new SchemaSettings({
|
|||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { name } = useCollection_deprecated();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { componentNamePrefix } = useBlockTemplateContext();
|
||||||
const defaultResource =
|
const defaultResource =
|
||||||
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
componentName: 'List',
|
componentName: `${componentNamePrefix}List`,
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
resourceName: defaultResource,
|
resourceName: defaultResource,
|
||||||
};
|
};
|
||||||
|
@ -44,7 +44,7 @@ export const TableBlockInitializer = ({
|
|||||||
<DataBlockInitializer
|
<DataBlockInitializer
|
||||||
{...itemConfig}
|
{...itemConfig}
|
||||||
icon={<TableOutlined />}
|
icon={<TableOutlined />}
|
||||||
componentType={'Table'}
|
componentType={`Table`}
|
||||||
onCreateBlockSchema={async (options) => {
|
onCreateBlockSchema={async (options) => {
|
||||||
if (createBlockSchema) {
|
if (createBlockSchema) {
|
||||||
return createBlockSchema(options);
|
return createBlockSchema(options);
|
||||||
|
@ -23,8 +23,8 @@ import { SchemaSettingsSortField } from '../../../../schema-settings/SchemaSetti
|
|||||||
import { SchemaSettingsTemplate } from '../../../../schema-settings/SchemaSettingsTemplate';
|
import { SchemaSettingsTemplate } from '../../../../schema-settings/SchemaSettingsTemplate';
|
||||||
import { setDefaultSortingRulesSchemaSettingsItem } from '../../../../schema-settings/setDefaultSortingRulesSchemaSettingsItem';
|
import { setDefaultSortingRulesSchemaSettingsItem } from '../../../../schema-settings/setDefaultSortingRulesSchemaSettingsItem';
|
||||||
import { setTheDataScopeSchemaSettingsItem } from '../../../../schema-settings/setTheDataScopeSchemaSettingsItem';
|
import { setTheDataScopeSchemaSettingsItem } from '../../../../schema-settings/setTheDataScopeSchemaSettingsItem';
|
||||||
|
import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider';
|
||||||
import { setDataLoadingModeSettingsItem } from '../details-multi/setDataLoadingModeSettingsItem';
|
import { setDataLoadingModeSettingsItem } from '../details-multi/setDataLoadingModeSettingsItem';
|
||||||
import { useCollection } from '../../../../data-source';
|
|
||||||
|
|
||||||
export const tableBlockSettings = new SchemaSettings({
|
export const tableBlockSettings = new SchemaSettings({
|
||||||
name: 'blockSettings:table',
|
name: 'blockSettings:table',
|
||||||
@ -212,10 +212,11 @@ export const tableBlockSettings = new SchemaSettings({
|
|||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { name } = useCollection_deprecated();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { componentNamePrefix } = useBlockTemplateContext();
|
||||||
const defaultResource =
|
const defaultResource =
|
||||||
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
return {
|
return {
|
||||||
componentName: 'Table',
|
componentName: `${componentNamePrefix}Table`,
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
resourceName: defaultResource,
|
resourceName: defaultResource,
|
||||||
};
|
};
|
||||||
|
@ -11,9 +11,9 @@ import { TableOutlined } from '@ant-design/icons';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { useSchemaInitializer, useSchemaInitializerItem } from '../../../../application';
|
import { useSchemaInitializer, useSchemaInitializerItem } from '../../../../application';
|
||||||
import { createCollapseBlockSchema } from './createFilterCollapseBlockSchema';
|
|
||||||
import { DataBlockInitializer } from '../../../../schema-initializer/items/DataBlockInitializer';
|
|
||||||
import { Collection, CollectionFieldOptions } from '../../../../data-source';
|
import { Collection, CollectionFieldOptions } from '../../../../data-source';
|
||||||
|
import { DataBlockInitializer } from '../../../../schema-initializer/items/DataBlockInitializer';
|
||||||
|
import { createCollapseBlockSchema } from './createFilterCollapseBlockSchema';
|
||||||
|
|
||||||
export const FilterCollapseBlockInitializer = ({
|
export const FilterCollapseBlockInitializer = ({
|
||||||
filterCollections,
|
filterCollections,
|
||||||
@ -32,7 +32,7 @@ export const FilterCollapseBlockInitializer = ({
|
|||||||
{...itemConfig}
|
{...itemConfig}
|
||||||
onlyCurrentDataSource={onlyCurrentDataSource}
|
onlyCurrentDataSource={onlyCurrentDataSource}
|
||||||
icon={<TableOutlined />}
|
icon={<TableOutlined />}
|
||||||
componentType={'FilterCollapse'}
|
componentType={`FilterCollapse`}
|
||||||
onCreateBlockSchema={async ({ item }) => {
|
onCreateBlockSchema={async ({ item }) => {
|
||||||
const schema = createCollapseBlockSchema({
|
const schema = createCollapseBlockSchema({
|
||||||
dataSource: item.dataSource,
|
dataSource: item.dataSource,
|
||||||
|
@ -12,10 +12,11 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
||||||
import { useCollection_deprecated } from '../../../../collection-manager';
|
import { useCollection_deprecated } from '../../../../collection-manager';
|
||||||
import { FilterBlockType } from '../../../../filter-provider';
|
import { FilterBlockType } from '../../../../filter-provider';
|
||||||
|
import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem';
|
||||||
import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem';
|
import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem';
|
||||||
import { SchemaSettingsConnectDataBlocks } from '../../../../schema-settings/SchemaSettingsConnectDataBlocks';
|
import { SchemaSettingsConnectDataBlocks } from '../../../../schema-settings/SchemaSettingsConnectDataBlocks';
|
||||||
import { SchemaSettingsTemplate } from '../../../../schema-settings/SchemaSettingsTemplate';
|
import { SchemaSettingsTemplate } from '../../../../schema-settings/SchemaSettingsTemplate';
|
||||||
import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem';
|
import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider';
|
||||||
|
|
||||||
export const filterCollapseBlockSettings = new SchemaSettings({
|
export const filterCollapseBlockSettings = new SchemaSettings({
|
||||||
name: 'blockSettings:filterCollapse',
|
name: 'blockSettings:filterCollapse',
|
||||||
@ -34,11 +35,12 @@ export const filterCollapseBlockSettings = new SchemaSettings({
|
|||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { name } = useCollection_deprecated();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { componentNamePrefix } = useBlockTemplateContext();
|
||||||
const defaultResource =
|
const defaultResource =
|
||||||
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
componentName: 'FilterCollapse',
|
componentName: `${componentNamePrefix}FilterCollapse`,
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
resourceName: defaultResource,
|
resourceName: defaultResource,
|
||||||
};
|
};
|
||||||
|
@ -10,9 +10,10 @@
|
|||||||
import { FormOutlined } from '@ant-design/icons';
|
import { FormOutlined } from '@ant-design/icons';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useSchemaInitializer, useSchemaInitializerItem } from '../../../../application';
|
import { useSchemaInitializer, useSchemaInitializerItem } from '../../../../application';
|
||||||
import { createFilterFormBlockSchema } from './createFilterFormBlockSchema';
|
|
||||||
import { FilterBlockInitializer } from '../../../../schema-initializer/items/FilterBlockInitializer';
|
|
||||||
import { Collection, CollectionFieldOptions } from '../../../../data-source';
|
import { Collection, CollectionFieldOptions } from '../../../../data-source';
|
||||||
|
import { FilterBlockInitializer } from '../../../../schema-initializer/items/FilterBlockInitializer';
|
||||||
|
import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider';
|
||||||
|
import { createFilterFormBlockSchema } from './createFilterFormBlockSchema';
|
||||||
|
|
||||||
export const FilterFormBlockInitializer = ({
|
export const FilterFormBlockInitializer = ({
|
||||||
filterCollections,
|
filterCollections,
|
||||||
@ -25,13 +26,14 @@ export const FilterFormBlockInitializer = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const itemConfig = useSchemaInitializerItem();
|
const itemConfig = useSchemaInitializerItem();
|
||||||
const { insert } = useSchemaInitializer();
|
const { insert } = useSchemaInitializer();
|
||||||
|
const { componentNamePrefix } = useBlockTemplateContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FilterBlockInitializer
|
<FilterBlockInitializer
|
||||||
{...itemConfig}
|
{...itemConfig}
|
||||||
icon={<FormOutlined />}
|
icon={<FormOutlined />}
|
||||||
onlyCurrentDataSource={onlyCurrentDataSource}
|
onlyCurrentDataSource={onlyCurrentDataSource}
|
||||||
componentType={'FilterFormItem'}
|
componentType={`${componentNamePrefix}FilterFormItem`}
|
||||||
templateWrap={(templateSchema, { item }) => {
|
templateWrap={(templateSchema, { item }) => {
|
||||||
const s = createFilterFormBlockSchema({
|
const s = createFilterFormBlockSchema({
|
||||||
templateSchema: templateSchema,
|
templateSchema: templateSchema,
|
||||||
|
@ -11,11 +11,13 @@ import { useFieldSchema } from '@formily/react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
||||||
import { useCollection_deprecated } from '../../../../collection-manager';
|
import { useCollection_deprecated } from '../../../../collection-manager';
|
||||||
|
import { useCollection } from '../../../../data-source/collection/CollectionProvider';
|
||||||
import { FilterBlockType } from '../../../../filter-provider';
|
import { FilterBlockType } from '../../../../filter-provider';
|
||||||
import { SchemaSettingsFormItemTemplate, SchemaSettingsLinkageRules } from '../../../../schema-settings';
|
import { SchemaSettingsFormItemTemplate, SchemaSettingsLinkageRules } from '../../../../schema-settings';
|
||||||
|
import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem';
|
||||||
import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem';
|
import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem';
|
||||||
import { SchemaSettingsConnectDataBlocks } from '../../../../schema-settings/SchemaSettingsConnectDataBlocks';
|
import { SchemaSettingsConnectDataBlocks } from '../../../../schema-settings/SchemaSettingsConnectDataBlocks';
|
||||||
import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem';
|
import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider';
|
||||||
|
|
||||||
export const filterFormBlockSettings = new SchemaSettings({
|
export const filterFormBlockSettings = new SchemaSettings({
|
||||||
name: 'blockSettings:filterForm',
|
name: 'blockSettings:filterForm',
|
||||||
@ -32,12 +34,13 @@ export const filterFormBlockSettings = new SchemaSettings({
|
|||||||
name: 'formItemTemplate',
|
name: 'formItemTemplate',
|
||||||
Component: SchemaSettingsFormItemTemplate,
|
Component: SchemaSettingsFormItemTemplate,
|
||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { componentNamePrefix } = useBlockTemplateContext();
|
||||||
|
const { name } = useCollection();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const defaultResource =
|
const defaultResource =
|
||||||
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
return {
|
return {
|
||||||
componentName: 'FilterFormItem',
|
componentName: `${componentNamePrefix}FilterFormItem`,
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
resourceName: defaultResource,
|
resourceName: defaultResource,
|
||||||
};
|
};
|
||||||
|
@ -16,8 +16,8 @@ import { ComposedActionDrawer } from './types';
|
|||||||
|
|
||||||
export const ActionContainer: ComposedActionDrawer = observer(
|
export const ActionContainer: ComposedActionDrawer = observer(
|
||||||
(props: any) => {
|
(props: any) => {
|
||||||
const { openMode } = useActionContext();
|
const { getComponentByOpenMode, defaultOpenMode } = useOpenModeContext();
|
||||||
const { getComponentByOpenMode } = useOpenModeContext();
|
const { openMode = defaultOpenMode } = useActionContext();
|
||||||
const { currentLevel } = useCurrentPopupContext();
|
const { currentLevel } = useCurrentPopupContext();
|
||||||
|
|
||||||
const Component = getComponentByOpenMode(openMode);
|
const Component = getComponentByOpenMode(openMode);
|
||||||
|
@ -43,19 +43,19 @@ export const ActionContextProvider: React.FC<ActionContextProps & { value?: Acti
|
|||||||
|
|
||||||
const useBlockServiceInActionButton = () => {
|
const useBlockServiceInActionButton = () => {
|
||||||
const { params } = useCurrentPopupContext();
|
const { params } = useCurrentPopupContext();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
const popupUidWithoutOpened = useFieldSchema()?.['x-uid'];
|
const popupUidWithoutOpened = useFieldSchema()?.['x-uid'];
|
||||||
const service = useDataBlockRequest();
|
const service = useDataBlockRequest();
|
||||||
const currentPopupUid = params?.popupuid;
|
const currentPopupUid = params?.popupuid;
|
||||||
|
|
||||||
// By using caching, we solve the problem of not being able to obtain the correct service when closing a popup through a URL
|
// 把 service 存起来
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// This case refers to when the current button is rendered on a page or in a popup
|
|
||||||
if (popupUidWithoutOpened && currentPopupUid !== popupUidWithoutOpened) {
|
if (popupUidWithoutOpened && currentPopupUid !== popupUidWithoutOpened) {
|
||||||
storeBlockService(popupUidWithoutOpened, { service });
|
storeBlockService(popupUidWithoutOpened, { service });
|
||||||
}
|
}
|
||||||
}, [popupUidWithoutOpened, service, currentPopupUid]);
|
}, [popupUidWithoutOpened, service, currentPopupUid, fieldSchema]);
|
||||||
|
|
||||||
// This case refers to when the current button is closed as a popup (the button's uid is the same as the popup's uid)
|
// 关闭弹窗时,获取到对应的 service
|
||||||
if (currentPopupUid === popupUidWithoutOpened) {
|
if (currentPopupUid === popupUidWithoutOpened) {
|
||||||
return getBlockService(currentPopupUid)?.service || service;
|
return getBlockService(currentPopupUid)?.service || service;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import { ISchema, observer, useForm } from '@formily/react';
|
import { ISchema, observer, useForm } from '@formily/react';
|
||||||
import {
|
import {
|
||||||
Action,
|
Action,
|
||||||
|
CustomRouterContextProvider,
|
||||||
Form,
|
Form,
|
||||||
FormItem,
|
FormItem,
|
||||||
Input,
|
Input,
|
||||||
@ -11,6 +10,7 @@ import {
|
|||||||
useActionContext,
|
useActionContext,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Router } from 'react-router-dom';
|
||||||
|
|
||||||
const useCloseAction = () => {
|
const useCloseAction = () => {
|
||||||
const { setVisible } = useActionContext();
|
const { setVisible } = useActionContext();
|
||||||
@ -67,8 +67,12 @@ const schema: ISchema = {
|
|||||||
|
|
||||||
export default observer(() => {
|
export default observer(() => {
|
||||||
return (
|
return (
|
||||||
|
<Router location={window.location} navigator={null}>
|
||||||
|
<CustomRouterContextProvider>
|
||||||
<SchemaComponentProvider scope={{ useCloseAction }} components={{ Form, Action, Input, FormItem }}>
|
<SchemaComponentProvider scope={{ useCloseAction }} components={{ Form, Action, Input, FormItem }}>
|
||||||
<SchemaComponent schema={schema} />
|
<SchemaComponent schema={schema} />
|
||||||
</SchemaComponentProvider>
|
</SchemaComponentProvider>
|
||||||
|
</CustomRouterContextProvider>
|
||||||
|
</Router>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
|
|
||||||
|
|
||||||
import { ISchema, observer, useForm } from '@formily/react';
|
import { ISchema, observer, useForm } from '@formily/react';
|
||||||
import {
|
import {
|
||||||
Action,
|
Action,
|
||||||
ActionContextProvider,
|
ActionContextProvider,
|
||||||
|
CustomRouterContextProvider,
|
||||||
Form,
|
Form,
|
||||||
FormItem,
|
FormItem,
|
||||||
Input,
|
Input,
|
||||||
@ -12,6 +11,7 @@ import {
|
|||||||
useActionContext,
|
useActionContext,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import { Router } from 'react-router-dom';
|
||||||
|
|
||||||
const useCloseAction = () => {
|
const useCloseAction = () => {
|
||||||
const { setVisible } = useActionContext();
|
const { setVisible } = useActionContext();
|
||||||
@ -59,11 +59,15 @@ const schema: ISchema = {
|
|||||||
export default observer(() => {
|
export default observer(() => {
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
return (
|
return (
|
||||||
|
<Router location={window.location} navigator={null}>
|
||||||
|
<CustomRouterContextProvider>
|
||||||
<SchemaComponentProvider components={{ Form, Action, Input, FormItem }}>
|
<SchemaComponentProvider components={{ Form, Action, Input, FormItem }}>
|
||||||
<ActionContextProvider value={{ visible, setVisible }}>
|
<ActionContextProvider value={{ visible, setVisible }}>
|
||||||
<a onClick={() => setVisible(true)}>Open</a>
|
<a onClick={() => setVisible(true)}>Open</a>
|
||||||
<SchemaComponent scope={{ useCloseAction }} schema={schema} />
|
<SchemaComponent scope={{ useCloseAction }} schema={schema} />
|
||||||
</ActionContextProvider>
|
</ActionContextProvider>
|
||||||
</SchemaComponentProvider>
|
</SchemaComponentProvider>
|
||||||
|
</CustomRouterContextProvider>
|
||||||
|
</Router>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import { ISchema, observer, useForm } from '@formily/react';
|
import { ISchema, observer, useForm } from '@formily/react';
|
||||||
import {
|
import {
|
||||||
Action,
|
Action,
|
||||||
|
CustomRouterContextProvider,
|
||||||
Form,
|
Form,
|
||||||
FormItem,
|
FormItem,
|
||||||
Input,
|
Input,
|
||||||
@ -11,6 +10,7 @@ import {
|
|||||||
useActionContext,
|
useActionContext,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Router } from 'react-router-dom';
|
||||||
|
|
||||||
const useCloseAction = () => {
|
const useCloseAction = () => {
|
||||||
const { setVisible } = useActionContext();
|
const { setVisible } = useActionContext();
|
||||||
@ -55,8 +55,12 @@ const schema: ISchema = {
|
|||||||
|
|
||||||
export default observer(() => {
|
export default observer(() => {
|
||||||
return (
|
return (
|
||||||
|
<Router location={window.location} navigator={null}>
|
||||||
|
<CustomRouterContextProvider>
|
||||||
<SchemaComponentProvider scope={{ useCloseAction }} components={{ Form, Action, Input, FormItem }}>
|
<SchemaComponentProvider scope={{ useCloseAction }} components={{ Form, Action, Input, FormItem }}>
|
||||||
<SchemaComponent schema={schema} />
|
<SchemaComponent schema={schema} />
|
||||||
</SchemaComponentProvider>
|
</SchemaComponentProvider>
|
||||||
|
</CustomRouterContextProvider>
|
||||||
|
</Router>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,81 @@
|
|||||||
|
/**
|
||||||
|
* 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 _ from 'lodash';
|
||||||
|
import React, { createContext, useCallback, useMemo } from 'react';
|
||||||
|
import { AssociationSelect } from './AssociationSelect';
|
||||||
|
import { InternalFileManager } from './FileManager';
|
||||||
|
import { InternalCascadeSelect } from './InternalCascadeSelect';
|
||||||
|
import { InternalNester } from './InternalNester';
|
||||||
|
import { InternalPicker } from './InternalPicker';
|
||||||
|
import { InternalPopoverNester } from './InternalPopoverNester';
|
||||||
|
import { InternalSubTable } from './InternalSubTable';
|
||||||
|
|
||||||
|
export enum AssociationFieldMode {
|
||||||
|
Picker = 'Picker',
|
||||||
|
Nester = 'Nester',
|
||||||
|
PopoverNester = 'PopoverNester',
|
||||||
|
Select = 'Select',
|
||||||
|
SubTable = 'SubTable',
|
||||||
|
FileManager = 'FileManager',
|
||||||
|
CascadeSelect = 'CascadeSelect',
|
||||||
|
Tag = 'Tag',
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AssociationFieldModeProviderProps {
|
||||||
|
modeToComponent: Partial<Record<AssociationFieldMode, React.FC | ((originalCom: React.FC) => React.FC)>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultModeToComponent = {
|
||||||
|
Picker: InternalPicker,
|
||||||
|
Nester: InternalNester,
|
||||||
|
PopoverNester: InternalPopoverNester,
|
||||||
|
Select: AssociationSelect,
|
||||||
|
SubTable: InternalSubTable,
|
||||||
|
FileManager: InternalFileManager,
|
||||||
|
CascadeSelect: InternalCascadeSelect,
|
||||||
|
};
|
||||||
|
|
||||||
|
const AssociationFieldModeContext = createContext<{
|
||||||
|
modeToComponent: AssociationFieldModeProviderProps['modeToComponent'];
|
||||||
|
getComponent: (mode: AssociationFieldMode) => React.FC;
|
||||||
|
}>({
|
||||||
|
modeToComponent: defaultModeToComponent,
|
||||||
|
getComponent: (mode: AssociationFieldMode) => {
|
||||||
|
return defaultModeToComponent[mode];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AssociationFieldModeProvider: React.FC<AssociationFieldModeProviderProps> = (props) => {
|
||||||
|
const modeContext = useAssociationFieldModeContext();
|
||||||
|
const modeToComponent = useMemo(
|
||||||
|
() => ({ ...modeContext.modeToComponent, ...props.modeToComponent }),
|
||||||
|
[modeContext.modeToComponent, props.modeToComponent],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getComponent = useCallback(
|
||||||
|
(mode: AssociationFieldMode) => {
|
||||||
|
const component = modeToComponent[mode];
|
||||||
|
|
||||||
|
if (_.isFunction(component)) {
|
||||||
|
return component(defaultModeToComponent[mode]) as React.FC;
|
||||||
|
}
|
||||||
|
|
||||||
|
return component || defaultModeToComponent[mode];
|
||||||
|
},
|
||||||
|
[modeToComponent],
|
||||||
|
);
|
||||||
|
|
||||||
|
const value = useMemo(() => ({ modeToComponent, getComponent }), [getComponent, modeToComponent]);
|
||||||
|
return <AssociationFieldModeContext.Provider value={value}>{props.children}</AssociationFieldModeContext.Provider>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useAssociationFieldModeContext = () => {
|
||||||
|
return React.useContext(AssociationFieldModeContext);
|
||||||
|
};
|
@ -13,14 +13,8 @@ import React from 'react';
|
|||||||
import { SchemaComponentOptions } from '../../';
|
import { SchemaComponentOptions } from '../../';
|
||||||
import { useAssociationCreateActionProps as useCAP } from '../../../block-provider/hooks';
|
import { useAssociationCreateActionProps as useCAP } from '../../../block-provider/hooks';
|
||||||
import { useCollection_deprecated } from '../../../collection-manager';
|
import { useCollection_deprecated } from '../../../collection-manager';
|
||||||
|
import { useAssociationFieldModeContext } from './AssociationFieldModeProvider';
|
||||||
import { AssociationFieldProvider } from './AssociationFieldProvider';
|
import { AssociationFieldProvider } from './AssociationFieldProvider';
|
||||||
import { AssociationSelect } from './AssociationSelect';
|
|
||||||
import { InternalFileManager } from './FileManager';
|
|
||||||
import { InternalCascadeSelect } from './InternalCascadeSelect';
|
|
||||||
import { InternalNester } from './InternalNester';
|
|
||||||
import { InternalPicker } from './InternalPicker';
|
|
||||||
import { InternalPopoverNester } from './InternalPopoverNester';
|
|
||||||
import { InternalSubTable } from './InternalSubTable';
|
|
||||||
import { CreateRecordAction } from './components/CreateRecordAction';
|
import { CreateRecordAction } from './components/CreateRecordAction';
|
||||||
import { useAssociationFieldContext } from './hooks';
|
import { useAssociationFieldContext } from './hooks';
|
||||||
|
|
||||||
@ -30,6 +24,7 @@ const EditableAssociationField = observer(
|
|||||||
const field: Field = useField();
|
const field: Field = useField();
|
||||||
const form = useForm();
|
const form = useForm();
|
||||||
const { options: collectionField, currentMode } = useAssociationFieldContext();
|
const { options: collectionField, currentMode } = useAssociationFieldContext();
|
||||||
|
const { getComponent } = useAssociationFieldModeContext();
|
||||||
|
|
||||||
const useCreateActionProps = () => {
|
const useCreateActionProps = () => {
|
||||||
const { onClick } = useCAP();
|
const { onClick } = useCAP();
|
||||||
@ -57,15 +52,11 @@ const EditableAssociationField = observer(
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Component = getComponent(currentMode);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SchemaComponentOptions scope={{ useCreateActionProps }} components={{ CreateRecordAction }}>
|
<SchemaComponentOptions scope={{ useCreateActionProps }} components={{ CreateRecordAction }}>
|
||||||
{currentMode === 'Picker' && <InternalPicker {...props} />}
|
<Component {...props} />
|
||||||
{currentMode === 'Nester' && <InternalNester {...props} />}
|
|
||||||
{currentMode === 'PopoverNester' && <InternalPopoverNester {...props} />}
|
|
||||||
{currentMode === 'Select' && <AssociationSelect {...props} />}
|
|
||||||
{currentMode === 'SubTable' && <InternalSubTable {...props} />}
|
|
||||||
{currentMode === 'FileManager' && <InternalFileManager {...props} />}
|
|
||||||
{currentMode === 'CascadeSelect' && <InternalCascadeSelect {...props} />}
|
|
||||||
</SchemaComponentOptions>
|
</SchemaComponentOptions>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
import { EditOutlined } from '@ant-design/icons';
|
import { EditOutlined } from '@ant-design/icons';
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { observer, useFieldSchema } from '@formily/react';
|
import { observer, useFieldSchema } from '@formily/react';
|
||||||
import React, { useContext, useRef, useState } from 'react';
|
import React, { useCallback, useContext, useMemo, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ActionContext, ActionContextProvider } from '../action/context';
|
import { ActionContext, ActionContextProvider } from '../action/context';
|
||||||
import { useGetAriaLabelOfPopover } from '../action/hooks/useGetAriaLabelOfPopover';
|
import { useGetAriaLabelOfPopover } from '../action/hooks/useGetAriaLabelOfPopover';
|
||||||
@ -22,6 +22,7 @@ import { useAssociationFieldContext } from './hooks';
|
|||||||
|
|
||||||
const InternalPopoverNesterContentCss = css`
|
const InternalPopoverNesterContentCss = css`
|
||||||
min-width: 600px;
|
min-width: 600px;
|
||||||
|
max-width: 800px;
|
||||||
max-height: 440px;
|
max-height: 440px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
.ant-card {
|
.ant-card {
|
||||||
@ -30,7 +31,16 @@ const InternalPopoverNesterContentCss = css`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const InternalPopoverNester = observer(
|
export const InternalPopoverNester = observer(
|
||||||
(props) => {
|
(props: {
|
||||||
|
Container?: (props: {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
trigger: 'click' | 'hover';
|
||||||
|
content: React.ReactElement;
|
||||||
|
children: React.ReactElement;
|
||||||
|
}) => React.ReactElement;
|
||||||
|
children?: React.ReactElement;
|
||||||
|
}) => {
|
||||||
const { options } = useAssociationFieldContext();
|
const { options } = useAssociationFieldContext();
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -42,11 +52,7 @@ export const InternalPopoverNester = observer(
|
|||||||
shouldMountElement: true,
|
shouldMountElement: true,
|
||||||
};
|
};
|
||||||
const content = (
|
const content = (
|
||||||
<div
|
<div ref={ref} className={`${InternalPopoverNesterContentCss} popover-subform-container`}>
|
||||||
ref={ref}
|
|
||||||
style={{ minWidth: '600px', maxWidth: '800px', maxHeight: '440px', overflow: 'auto' }}
|
|
||||||
className={InternalPopoverNesterContentCss}
|
|
||||||
>
|
|
||||||
<InternalNester {...nesterProps} />
|
<InternalNester {...nesterProps} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -56,6 +62,11 @@ export const InternalPopoverNester = observer(
|
|||||||
getContainer: getContainer,
|
getContainer: getContainer,
|
||||||
};
|
};
|
||||||
const { getAriaLabel } = useGetAriaLabelOfPopover();
|
const { getAriaLabel } = useGetAriaLabelOfPopover();
|
||||||
|
const Container = props.Container || StablePopover;
|
||||||
|
const handleOpenChange = useCallback((open: boolean) => {
|
||||||
|
setVisible(open);
|
||||||
|
}, []);
|
||||||
|
const overlayStyle = useMemo(() => ({ padding: '0px' }), []);
|
||||||
|
|
||||||
if (process.env.__E2E__) {
|
if (process.env.__E2E__) {
|
||||||
useSetAriaLabelForPopover(visible);
|
useSetAriaLabelForPopover(visible);
|
||||||
@ -63,13 +74,13 @@ export const InternalPopoverNester = observer(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ActionContextProvider value={{ ...ctx, modalProps }}>
|
<ActionContextProvider value={{ ...ctx, modalProps }}>
|
||||||
<StablePopover
|
<Container
|
||||||
overlayStyle={{ padding: '0px' }}
|
overlayStyle={overlayStyle}
|
||||||
content={content}
|
content={content}
|
||||||
trigger="click"
|
trigger="click"
|
||||||
placement="topLeft"
|
placement="topLeft"
|
||||||
open={visible}
|
open={visible}
|
||||||
onOpenChange={(open) => setVisible(open)}
|
onOpenChange={handleOpenChange}
|
||||||
title={t(options?.uiSchema?.rawTitle)}
|
title={t(options?.uiSchema?.rawTitle)}
|
||||||
>
|
>
|
||||||
<span style={{ cursor: 'pointer', display: 'flex' }}>
|
<span style={{ cursor: 'pointer', display: 'flex' }}>
|
||||||
@ -82,7 +93,7 @@ export const InternalPopoverNester = observer(
|
|||||||
</div>
|
</div>
|
||||||
<EditOutlined style={{ display: 'inline-flex', margin: '5px' }} />
|
<EditOutlined style={{ display: 'inline-flex', margin: '5px' }} />
|
||||||
</span>
|
</span>
|
||||||
</StablePopover>
|
</Container>
|
||||||
{visible && (
|
{visible && (
|
||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
|
@ -25,6 +25,7 @@ import { getPath } from '../../../variables/utils/getPath';
|
|||||||
import { getVariableName } from '../../../variables/utils/getVariableName';
|
import { getVariableName } from '../../../variables/utils/getVariableName';
|
||||||
import { isVariable } from '../../../variables/utils/isVariable';
|
import { isVariable } from '../../../variables/utils/isVariable';
|
||||||
import { useDesignable } from '../../hooks';
|
import { useDesignable } from '../../hooks';
|
||||||
|
import { AssociationFieldMode } from './AssociationFieldModeProvider';
|
||||||
import { AssociationFieldContext } from './context';
|
import { AssociationFieldContext } from './context';
|
||||||
|
|
||||||
export const useInsertSchema = (component) => {
|
export const useInsertSchema = (component) => {
|
||||||
@ -51,7 +52,7 @@ export function useAssociationFieldContext<F extends GeneralField>() {
|
|||||||
return useContext(AssociationFieldContext) as {
|
return useContext(AssociationFieldContext) as {
|
||||||
options: any;
|
options: any;
|
||||||
field: F;
|
field: F;
|
||||||
currentMode: string;
|
currentMode: AssociationFieldMode;
|
||||||
allowMultiple?: boolean;
|
allowMultiple?: boolean;
|
||||||
allowDissociate?: boolean;
|
allowDissociate?: boolean;
|
||||||
};
|
};
|
||||||
|
@ -15,6 +15,7 @@ import { Nester } from './Nester';
|
|||||||
import { ReadPretty } from './ReadPretty';
|
import { ReadPretty } from './ReadPretty';
|
||||||
import { SubTable } from './SubTable';
|
import { SubTable } from './SubTable';
|
||||||
|
|
||||||
|
export { AssociationFieldModeProvider } from './AssociationFieldModeProvider';
|
||||||
export const AssociationField: any = connect(Editable, mapReadPretty(ReadPretty));
|
export const AssociationField: any = connect(Editable, mapReadPretty(ReadPretty));
|
||||||
|
|
||||||
AssociationField.SubTable = SubTable;
|
AssociationField.SubTable = SubTable;
|
||||||
|
@ -17,19 +17,25 @@ import { SchemaSettingsBlockTitleItem } from '../../../schema-settings/SchemaSet
|
|||||||
import { SchemaSettingsConnectDataBlocks } from '../../../schema-settings/SchemaSettingsConnectDataBlocks';
|
import { SchemaSettingsConnectDataBlocks } from '../../../schema-settings/SchemaSettingsConnectDataBlocks';
|
||||||
import { SchemaSettingsTemplate } from '../../../schema-settings/SchemaSettingsTemplate';
|
import { SchemaSettingsTemplate } from '../../../schema-settings/SchemaSettingsTemplate';
|
||||||
import { useSchemaTemplate } from '../../../schema-templates';
|
import { useSchemaTemplate } from '../../../schema-templates';
|
||||||
|
import { useBlockTemplateContext } from '../../../schema-templates/BlockTemplateProvider';
|
||||||
|
|
||||||
export const AssociationFilterBlockDesigner = () => {
|
export const AssociationFilterBlockDesigner = () => {
|
||||||
const { name, title } = useCollection_deprecated();
|
const { name, title } = useCollection_deprecated();
|
||||||
const template = useSchemaTemplate();
|
const template = useSchemaTemplate();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { componentNamePrefix } = useBlockTemplateContext();
|
||||||
const defaultResource =
|
const defaultResource =
|
||||||
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GeneralSchemaDesigner template={template} title={title || name}>
|
<GeneralSchemaDesigner template={template} title={title || name}>
|
||||||
<SchemaSettingsBlockTitleItem />
|
<SchemaSettingsBlockTitleItem />
|
||||||
<SchemaSettingsTemplate componentName={'FilterCollapse'} collectionName={name} resourceName={defaultResource} />
|
<SchemaSettingsTemplate
|
||||||
|
componentName={`${componentNamePrefix}FilterCollapse`}
|
||||||
|
collectionName={name}
|
||||||
|
resourceName={defaultResource}
|
||||||
|
/>
|
||||||
<SchemaSettingsConnectDataBlocks type={FilterBlockType.COLLAPSE} emptyDescription={t('No blocks to connect')} />
|
<SchemaSettingsConnectDataBlocks type={FilterBlockType.COLLAPSE} emptyDescription={t('No blocks to connect')} />
|
||||||
<SchemaSettingsDivider />
|
<SchemaSettingsDivider />
|
||||||
<SchemaSettingsRemove
|
<SchemaSettingsRemove
|
||||||
|
@ -99,6 +99,8 @@ const InternalAssociationSelect = connect(
|
|||||||
mapReadPretty(ReadPretty),
|
mapReadPretty(ReadPretty),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
InternalAssociationSelect.displayName = 'InternalAssociationSelect';
|
||||||
|
|
||||||
interface AssociationSelectInterface {
|
interface AssociationSelectInterface {
|
||||||
(props: any): React.ReactElement;
|
(props: any): React.ReactElement;
|
||||||
Designer: React.FC;
|
Designer: React.FC;
|
||||||
|
@ -30,6 +30,17 @@ export type FilterActionProps<T = {}> = ActionProps & {
|
|||||||
form?: Form;
|
form?: Form;
|
||||||
onSubmit?: (values: T) => void;
|
onSubmit?: (values: T) => void;
|
||||||
onReset?: (values: T) => void;
|
onReset?: (values: T) => void;
|
||||||
|
/**
|
||||||
|
* @default Popover
|
||||||
|
* 在移动端中,会使用移动端的 Popup 组件
|
||||||
|
*/
|
||||||
|
Container?: (props: {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
trigger: 'click' | 'hover';
|
||||||
|
content: React.ReactElement;
|
||||||
|
children: React.ReactElement;
|
||||||
|
}) => React.ReactElement;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FilterAction = withDynamicSchemaProps(
|
export const FilterAction = withDynamicSchemaProps(
|
||||||
@ -42,7 +53,7 @@ export const FilterAction = withDynamicSchemaProps(
|
|||||||
const form = useMemo<Form>(() => props.form || createForm(), []);
|
const form = useMemo<Form>(() => props.form || createForm(), []);
|
||||||
|
|
||||||
// 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema
|
// 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema
|
||||||
const { options, onSubmit, onReset, ...others } = useProps(props);
|
const { options, onSubmit, onReset, Container = StablePopover, ...others } = useProps(props);
|
||||||
|
|
||||||
const onOpenChange = useCallback((visible: boolean): void => {
|
const onOpenChange = useCallback((visible: boolean): void => {
|
||||||
setVisible(visible);
|
setVisible(visible);
|
||||||
@ -50,7 +61,7 @@ export const FilterAction = withDynamicSchemaProps(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<FilterActionContext.Provider value={{ field, fieldSchema, designable, dn }}>
|
<FilterActionContext.Provider value={{ field, fieldSchema, designable, dn }}>
|
||||||
<StablePopover
|
<Container
|
||||||
destroyTooltipOnHide
|
destroyTooltipOnHide
|
||||||
placement={'bottomLeft'}
|
placement={'bottomLeft'}
|
||||||
open={visible}
|
open={visible}
|
||||||
@ -112,8 +123,8 @@ export const FilterAction = withDynamicSchemaProps(
|
|||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Action {...others} title={field.title} />
|
<Action onClick={() => setVisible(!visible)} {...others} title={field.title} />
|
||||||
</StablePopover>
|
</Container>
|
||||||
</FilterActionContext.Provider>
|
</FilterActionContext.Provider>
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
@ -62,7 +62,7 @@ export const FilterItem = observer(
|
|||||||
return (
|
return (
|
||||||
// 添加 nc-filter-item 类名是为了帮助编写测试时更容易选中该元素
|
// 添加 nc-filter-item 类名是为了帮助编写测试时更容易选中该元素
|
||||||
<div style={style} className="nc-filter-item">
|
<div style={style} className="nc-filter-item">
|
||||||
<Space>
|
<Space wrap>
|
||||||
<Cascader
|
<Cascader
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
role="button"
|
role="button"
|
||||||
@ -90,6 +90,7 @@ export const FilterItem = observer(
|
|||||||
onChange={onOperatorsChange}
|
onChange={onOperatorsChange}
|
||||||
placeholder={t('Comparision')}
|
placeholder={t('Comparision')}
|
||||||
/>
|
/>
|
||||||
|
<Space>
|
||||||
{!operator?.noValue ? (
|
{!operator?.noValue ? (
|
||||||
<DynamicComponent value={value} schema={schema} collectionField={collectionField} onChange={setValue} />
|
<DynamicComponent value={value} schema={schema} collectionField={collectionField} onChange={setValue} />
|
||||||
) : null}
|
) : null}
|
||||||
@ -99,6 +100,7 @@ export const FilterItem = observer(
|
|||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
|
|
||||||
|
|
||||||
import { ISchema } from '@formily/json-schema';
|
import { ISchema } from '@formily/json-schema';
|
||||||
import { Filter, FilterAction, Input, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
import {
|
||||||
|
CustomRouterContextProvider,
|
||||||
|
Filter,
|
||||||
|
FilterAction,
|
||||||
|
Input,
|
||||||
|
SchemaComponent,
|
||||||
|
SchemaComponentProvider,
|
||||||
|
} from '@nocobase/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Router } from 'react-router-dom';
|
||||||
|
|
||||||
const options = [
|
const options = [
|
||||||
{
|
{
|
||||||
@ -99,8 +105,12 @@ const schema: ISchema = {
|
|||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
return (
|
return (
|
||||||
|
<Router location={window.location} navigator={null}>
|
||||||
|
<CustomRouterContextProvider>
|
||||||
<SchemaComponentProvider components={{ FilterAction, Filter, Input }} scope={{ options }}>
|
<SchemaComponentProvider components={{ FilterAction, Filter, Input }} scope={{ options }}>
|
||||||
<SchemaComponent schema={schema} />
|
<SchemaComponent schema={schema} />
|
||||||
</SchemaComponentProvider>
|
</SchemaComponentProvider>
|
||||||
|
</CustomRouterContextProvider>
|
||||||
|
</Router>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -15,6 +15,7 @@ import { useDetailsBlockContext } from '../../../block-provider/DetailsBlockProv
|
|||||||
import { useFormBlockContext } from '../../../block-provider/FormBlockProvider';
|
import { useFormBlockContext } from '../../../block-provider/FormBlockProvider';
|
||||||
import { useCollection_deprecated } from '../../../collection-manager';
|
import { useCollection_deprecated } from '../../../collection-manager';
|
||||||
import { useSortFields } from '../../../collection-manager/action-hooks';
|
import { useSortFields } from '../../../collection-manager/action-hooks';
|
||||||
|
import { useCollection } from '../../../data-source/collection/CollectionProvider';
|
||||||
import { setDataLoadingModeSettingsItem } from '../../../modules/blocks/data-blocks/details-multi/setDataLoadingModeSettingsItem';
|
import { setDataLoadingModeSettingsItem } from '../../../modules/blocks/data-blocks/details-multi/setDataLoadingModeSettingsItem';
|
||||||
import {
|
import {
|
||||||
SchemaSettingsDataTemplates,
|
SchemaSettingsDataTemplates,
|
||||||
@ -25,6 +26,7 @@ import { SchemaSettingsBlockHeightItem } from '../../../schema-settings/SchemaSe
|
|||||||
import { SchemaSettingsBlockTitleItem } from '../../../schema-settings/SchemaSettingsBlockTitleItem';
|
import { SchemaSettingsBlockTitleItem } from '../../../schema-settings/SchemaSettingsBlockTitleItem';
|
||||||
import { SchemaSettingsDataScope } from '../../../schema-settings/SchemaSettingsDataScope';
|
import { SchemaSettingsDataScope } from '../../../schema-settings/SchemaSettingsDataScope';
|
||||||
import { SchemaSettingsTemplate } from '../../../schema-settings/SchemaSettingsTemplate';
|
import { SchemaSettingsTemplate } from '../../../schema-settings/SchemaSettingsTemplate';
|
||||||
|
import { useBlockTemplateContext } from '../../../schema-templates/BlockTemplateProvider';
|
||||||
import { useDesignable } from '../../hooks';
|
import { useDesignable } from '../../hooks';
|
||||||
import { removeNullCondition } from '../filter';
|
import { removeNullCondition } from '../filter';
|
||||||
|
|
||||||
@ -74,12 +76,13 @@ export const formSettings = new SchemaSettings({
|
|||||||
name: 'formItemTemplate',
|
name: 'formItemTemplate',
|
||||||
Component: SchemaSettingsFormItemTemplate,
|
Component: SchemaSettingsFormItemTemplate,
|
||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { componentNamePrefix } = useBlockTemplateContext();
|
||||||
|
const { name } = useCollection();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const defaultResource =
|
const defaultResource =
|
||||||
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
return {
|
return {
|
||||||
componentName: 'FormItem',
|
componentName: `${componentNamePrefix}FormItem`,
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
resourceName: defaultResource,
|
resourceName: defaultResource,
|
||||||
};
|
};
|
||||||
@ -127,13 +130,14 @@ export const readPrettyFormSettings = new SchemaSettings({
|
|||||||
name: 'formItemTemplate',
|
name: 'formItemTemplate',
|
||||||
Component: SchemaSettingsFormItemTemplate,
|
Component: SchemaSettingsFormItemTemplate,
|
||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { componentNamePrefix } = useBlockTemplateContext();
|
||||||
|
const { name } = useCollection();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const defaultResource =
|
const defaultResource =
|
||||||
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
return {
|
return {
|
||||||
insertAdjacentPosition: 'beforeEnd',
|
insertAdjacentPosition: 'beforeEnd',
|
||||||
componentName: 'ReadPrettyFormItem',
|
componentName: `${componentNamePrefix}ReadPrettyFormItem`,
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
resourceName: defaultResource,
|
resourceName: defaultResource,
|
||||||
};
|
};
|
||||||
@ -336,10 +340,11 @@ export const formDetailsSettings = new SchemaSettings({
|
|||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { name } = useCollection_deprecated();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { componentNamePrefix } = useBlockTemplateContext();
|
||||||
const defaultResource =
|
const defaultResource =
|
||||||
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
return {
|
return {
|
||||||
componentName: 'Details',
|
componentName: `${componentNamePrefix}Details`,
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
resourceName: defaultResource,
|
resourceName: defaultResource,
|
||||||
};
|
};
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
import { SchemaSettings } from '../../../application/schema-settings';
|
import { SchemaSettings } from '../../../application/schema-settings';
|
||||||
import { useCollection_deprecated } from '../../../collection-manager';
|
import { useCollection_deprecated } from '../../../collection-manager';
|
||||||
import { SchemaSettingsTemplate } from '../../../schema-settings/SchemaSettingsTemplate';
|
import { SchemaSettingsTemplate } from '../../../schema-settings/SchemaSettingsTemplate';
|
||||||
|
import { useBlockTemplateContext } from '../../../schema-templates/BlockTemplateProvider';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
@ -22,8 +23,9 @@ export const formV1Settings = new SchemaSettings({
|
|||||||
Component: SchemaSettingsTemplate,
|
Component: SchemaSettingsTemplate,
|
||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { name } = useCollection_deprecated();
|
||||||
|
const { componentNamePrefix } = useBlockTemplateContext();
|
||||||
return {
|
return {
|
||||||
componentName: 'Form',
|
componentName: `${componentNamePrefix}Form`,
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -19,6 +19,7 @@ import { useCollection_deprecated } from '../../../collection-manager';
|
|||||||
import { GeneralSchemaDesigner, SchemaSettingsDivider, SchemaSettingsRemove } from '../../../schema-settings';
|
import { GeneralSchemaDesigner, SchemaSettingsDivider, SchemaSettingsRemove } from '../../../schema-settings';
|
||||||
import { SchemaSettingsTemplate } from '../../../schema-settings/SchemaSettingsTemplate';
|
import { SchemaSettingsTemplate } from '../../../schema-settings/SchemaSettingsTemplate';
|
||||||
import { useSchemaTemplate } from '../../../schema-templates';
|
import { useSchemaTemplate } from '../../../schema-templates';
|
||||||
|
import { useBlockTemplateContext } from '../../../schema-templates/BlockTemplateProvider';
|
||||||
|
|
||||||
type Opts = Options<any, any> & { uid?: string };
|
type Opts = Options<any, any> & { uid?: string };
|
||||||
|
|
||||||
@ -130,9 +131,10 @@ export const Form: React.FC<FormProps> & { Designer?: any } = observer(
|
|||||||
Form.Designer = function Designer() {
|
Form.Designer = function Designer() {
|
||||||
const { name, title } = useCollection_deprecated();
|
const { name, title } = useCollection_deprecated();
|
||||||
const template = useSchemaTemplate();
|
const template = useSchemaTemplate();
|
||||||
|
const { componentNamePrefix } = useBlockTemplateContext();
|
||||||
return (
|
return (
|
||||||
<GeneralSchemaDesigner template={template} title={title || name}>
|
<GeneralSchemaDesigner template={template} title={title || name}>
|
||||||
<SchemaSettingsTemplate componentName={'Form'} collectionName={name} />
|
<SchemaSettingsTemplate componentName={`${componentNamePrefix}Form`} collectionName={name} />
|
||||||
<SchemaSettingsDivider />
|
<SchemaSettingsDivider />
|
||||||
<SchemaSettingsRemove
|
<SchemaSettingsRemove
|
||||||
removeParentsIfNoChildren
|
removeParentsIfNoChildren
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
|
|
||||||
|
|
||||||
import { FormItem, Input } from '@formily/antd-v5';
|
import { FormItem, Input } from '@formily/antd-v5';
|
||||||
import { ISchema, observer, useForm } from '@formily/react';
|
import { ISchema, observer, useForm } from '@formily/react';
|
||||||
import { Action, Form, SchemaComponent, SchemaComponentProvider, useCloseAction } from '@nocobase/client';
|
import {
|
||||||
|
Action,
|
||||||
|
CustomRouterContextProvider,
|
||||||
|
Form,
|
||||||
|
SchemaComponent,
|
||||||
|
SchemaComponentProvider,
|
||||||
|
useCloseAction,
|
||||||
|
} from '@nocobase/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Router } from 'react-router-dom';
|
||||||
|
|
||||||
const schema: ISchema = {
|
const schema: ISchema = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@ -60,8 +66,12 @@ const Output = observer(
|
|||||||
|
|
||||||
export default observer(() => {
|
export default observer(() => {
|
||||||
return (
|
return (
|
||||||
|
<Router location={window.location} navigator={null}>
|
||||||
|
<CustomRouterContextProvider>
|
||||||
<SchemaComponentProvider scope={{ useCloseAction }} components={{ Output, Form, Action, Input, FormItem }}>
|
<SchemaComponentProvider scope={{ useCloseAction }} components={{ Output, Form, Action, Input, FormItem }}>
|
||||||
<SchemaComponent schema={schema} />
|
<SchemaComponent schema={schema} />
|
||||||
</SchemaComponentProvider>
|
</SchemaComponentProvider>
|
||||||
|
</CustomRouterContextProvider>
|
||||||
|
</Router>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
|
|
||||||
|
|
||||||
import { FormItem, Input } from '@formily/antd-v5';
|
import { FormItem, Input } from '@formily/antd-v5';
|
||||||
import { ISchema, observer, useForm } from '@formily/react';
|
import { ISchema, observer, useForm } from '@formily/react';
|
||||||
import { Action, Form, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
import { Action, CustomRouterContextProvider, Form, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
||||||
import { Card } from 'antd';
|
import { Card } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Router } from 'react-router-dom';
|
||||||
|
|
||||||
const schema: ISchema = {
|
const schema: ISchema = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@ -59,8 +58,12 @@ export default observer(() => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Router location={window.location} navigator={null}>
|
||||||
|
<CustomRouterContextProvider>
|
||||||
<SchemaComponentProvider scope={{ useSubmit }} components={{ Card, Output, Action, Form, Input, FormItem }}>
|
<SchemaComponentProvider scope={{ useSubmit }} components={{ Card, Output, Action, Form, Input, FormItem }}>
|
||||||
<SchemaComponent schema={schema} />
|
<SchemaComponent schema={schema} />
|
||||||
</SchemaComponentProvider>
|
</SchemaComponentProvider>
|
||||||
|
</CustomRouterContextProvider>
|
||||||
|
</Router>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
|
|
||||||
|
|
||||||
import { FormItem, Input } from '@formily/antd-v5';
|
import { FormItem, Input } from '@formily/antd-v5';
|
||||||
import { ISchema, observer, useForm } from '@formily/react';
|
import { ISchema, observer, useForm } from '@formily/react';
|
||||||
import { Action, Form, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
import { Action, CustomRouterContextProvider, Form, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
||||||
import { Card } from 'antd';
|
import { Card } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Router } from 'react-router-dom';
|
||||||
|
|
||||||
const schema: ISchema = {
|
const schema: ISchema = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@ -58,8 +57,12 @@ export default observer(() => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Router location={window.location} navigator={null}>
|
||||||
|
<CustomRouterContextProvider>
|
||||||
<SchemaComponentProvider scope={{ useSubmit }} components={{ Card, Output, Action, Form, Input, FormItem }}>
|
<SchemaComponentProvider scope={{ useSubmit }} components={{ Card, Output, Action, Form, Input, FormItem }}>
|
||||||
<SchemaComponent schema={schema} />
|
<SchemaComponent schema={schema} />
|
||||||
</SchemaComponentProvider>
|
</SchemaComponentProvider>
|
||||||
|
</CustomRouterContextProvider>
|
||||||
|
</Router>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
|
|
||||||
|
|
||||||
import { FormItem, Input } from '@formily/antd-v5';
|
import { FormItem, Input } from '@formily/antd-v5';
|
||||||
import { ISchema, observer, useForm } from '@formily/react';
|
import { ISchema, observer, useForm } from '@formily/react';
|
||||||
import { Action, Form, SchemaComponent, SchemaComponentProvider, useCloseAction } from '@nocobase/client';
|
import {
|
||||||
|
Action,
|
||||||
|
CustomRouterContextProvider,
|
||||||
|
Form,
|
||||||
|
SchemaComponent,
|
||||||
|
SchemaComponentProvider,
|
||||||
|
useCloseAction,
|
||||||
|
} from '@nocobase/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Router } from 'react-router-dom';
|
||||||
|
|
||||||
const schema: ISchema = {
|
const schema: ISchema = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@ -65,8 +71,12 @@ export default observer(() => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Router location={window.location} navigator={null}>
|
||||||
|
<CustomRouterContextProvider>
|
||||||
<SchemaComponentProvider scope={{ useCloseAction }} components={{ Output, Form, Action, Input, FormItem }}>
|
<SchemaComponentProvider scope={{ useCloseAction }} components={{ Output, Form, Action, Input, FormItem }}>
|
||||||
<SchemaComponent schema={schema} />
|
<SchemaComponent schema={schema} />
|
||||||
</SchemaComponentProvider>
|
</SchemaComponentProvider>
|
||||||
|
</CustomRouterContextProvider>
|
||||||
|
</Router>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
|
|
||||||
|
|
||||||
import { FormItem, Input } from '@formily/antd-v5';
|
import { FormItem, Input } from '@formily/antd-v5';
|
||||||
import { ISchema, observer, useForm } from '@formily/react';
|
import { ISchema, observer, useForm } from '@formily/react';
|
||||||
import {
|
import {
|
||||||
APIClientProvider,
|
APIClientProvider,
|
||||||
Action,
|
Action,
|
||||||
|
CustomRouterContextProvider,
|
||||||
Form,
|
Form,
|
||||||
SchemaComponent,
|
SchemaComponent,
|
||||||
SchemaComponentProvider,
|
SchemaComponentProvider,
|
||||||
@ -12,6 +11,7 @@ import {
|
|||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { Card, Space } from 'antd';
|
import { Card, Space } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Router } from 'react-router-dom';
|
||||||
import { apiClient } from './apiClient';
|
import { apiClient } from './apiClient';
|
||||||
|
|
||||||
const schema: ISchema = {
|
const schema: ISchema = {
|
||||||
@ -105,6 +105,8 @@ const useRefresh = () => {
|
|||||||
|
|
||||||
export default observer(() => {
|
export default observer(() => {
|
||||||
return (
|
return (
|
||||||
|
<Router location={window.location} navigator={null}>
|
||||||
|
<CustomRouterContextProvider>
|
||||||
<APIClientProvider apiClient={apiClient}>
|
<APIClientProvider apiClient={apiClient}>
|
||||||
<SchemaComponentProvider
|
<SchemaComponentProvider
|
||||||
scope={{ useSubmit, useRefresh }}
|
scope={{ useSubmit, useRefresh }}
|
||||||
@ -113,5 +115,7 @@ export default observer(() => {
|
|||||||
<SchemaComponent schema={schema} />
|
<SchemaComponent schema={schema} />
|
||||||
</SchemaComponentProvider>
|
</SchemaComponentProvider>
|
||||||
</APIClientProvider>
|
</APIClientProvider>
|
||||||
|
</CustomRouterContextProvider>
|
||||||
|
</Router>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
|
|
||||||
|
|
||||||
import { FormItem, Input } from '@formily/antd-v5';
|
import { FormItem, Input } from '@formily/antd-v5';
|
||||||
import { ISchema, observer, useForm } from '@formily/react';
|
import { ISchema, observer, useForm } from '@formily/react';
|
||||||
import { Action, Form, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
import { Action, CustomRouterContextProvider, Form, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
||||||
import { Card } from 'antd';
|
import { Card } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Router } from 'react-router-dom';
|
||||||
|
|
||||||
const schema: ISchema = {
|
const schema: ISchema = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@ -59,8 +58,12 @@ const useSubmit = () => {
|
|||||||
|
|
||||||
export default observer(() => {
|
export default observer(() => {
|
||||||
return (
|
return (
|
||||||
|
<Router location={window.location} navigator={null}>
|
||||||
|
<CustomRouterContextProvider>
|
||||||
<SchemaComponentProvider scope={{ useSubmit }} components={{ Card, Output, Action, Form, Input, FormItem }}>
|
<SchemaComponentProvider scope={{ useSubmit }} components={{ Card, Output, Action, Form, Input, FormItem }}>
|
||||||
<SchemaComponent schema={schema} />
|
<SchemaComponent schema={schema} />
|
||||||
</SchemaComponentProvider>
|
</SchemaComponentProvider>
|
||||||
|
</CustomRouterContextProvider>
|
||||||
|
</Router>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
|
|
||||||
|
|
||||||
import { FormItem, Input } from '@formily/antd-v5';
|
import { FormItem, Input } from '@formily/antd-v5';
|
||||||
import { ISchema, observer, useForm } from '@formily/react';
|
import { ISchema, observer, useForm } from '@formily/react';
|
||||||
import { Action, Form, FormUseValues, SchemaComponent, SchemaComponentProvider, useRequest } from '@nocobase/client';
|
import {
|
||||||
|
Action,
|
||||||
|
CustomRouterContextProvider,
|
||||||
|
Form,
|
||||||
|
FormUseValues,
|
||||||
|
SchemaComponent,
|
||||||
|
SchemaComponentProvider,
|
||||||
|
useRequest,
|
||||||
|
} from '@nocobase/client';
|
||||||
import { Card } from 'antd';
|
import { Card } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Router } from 'react-router-dom';
|
||||||
|
|
||||||
const schema: ISchema = {
|
const schema: ISchema = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@ -63,11 +70,15 @@ const useValues: FormUseValues = (opts) => {
|
|||||||
|
|
||||||
export default observer(() => {
|
export default observer(() => {
|
||||||
return (
|
return (
|
||||||
|
<Router location={window.location} navigator={null}>
|
||||||
|
<CustomRouterContextProvider>
|
||||||
<SchemaComponentProvider
|
<SchemaComponentProvider
|
||||||
scope={{ useSubmit, useValues }}
|
scope={{ useSubmit, useValues }}
|
||||||
components={{ Card, Output, Action, Form, Input, FormItem }}
|
components={{ Card, Output, Action, Form, Input, FormItem }}
|
||||||
>
|
>
|
||||||
<SchemaComponent schema={schema} />
|
<SchemaComponent schema={schema} />
|
||||||
</SchemaComponentProvider>
|
</SchemaComponentProvider>
|
||||||
|
</CustomRouterContextProvider>
|
||||||
|
</Router>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
|
|
||||||
|
|
||||||
import { FormItem } from '@formily/antd-v5';
|
import { FormItem } from '@formily/antd-v5';
|
||||||
import { ISchema, observer } from '@formily/react';
|
import { ISchema, observer } from '@formily/react';
|
||||||
import {
|
import {
|
||||||
Action,
|
Action,
|
||||||
ActionContextProvider,
|
ActionContextProvider,
|
||||||
|
CustomRouterContextProvider,
|
||||||
Form,
|
Form,
|
||||||
Input,
|
Input,
|
||||||
SchemaComponent,
|
SchemaComponent,
|
||||||
@ -15,6 +14,7 @@ import {
|
|||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Router } from 'react-router-dom';
|
||||||
|
|
||||||
const useValues = (options) => {
|
const useValues = (options) => {
|
||||||
const { visible } = useActionContext();
|
const { visible } = useActionContext();
|
||||||
@ -72,6 +72,8 @@ export default observer(() => {
|
|||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Router location={window.location} navigator={null}>
|
||||||
|
<CustomRouterContextProvider>
|
||||||
<SchemaComponentProvider components={{ Action, Input, FormItem, Form }} scope={{ useCloseAction }}>
|
<SchemaComponentProvider components={{ Action, Input, FormItem, Form }} scope={{ useCloseAction }}>
|
||||||
<ActionContextProvider value={{ visible, setVisible }}>
|
<ActionContextProvider value={{ visible, setVisible }}>
|
||||||
<Button
|
<Button
|
||||||
@ -84,5 +86,7 @@ export default observer(() => {
|
|||||||
<SchemaComponent schema={schema} />
|
<SchemaComponent schema={schema} />
|
||||||
</ActionContextProvider>
|
</ActionContextProvider>
|
||||||
</SchemaComponentProvider>
|
</SchemaComponentProvider>
|
||||||
|
</CustomRouterContextProvider>
|
||||||
|
</Router>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -17,7 +17,6 @@ import { useFormBlockContext } from '../../../block-provider/FormBlockProvider';
|
|||||||
import { useCollection_deprecated, useSortFields } from '../../../collection-manager';
|
import { useCollection_deprecated, useSortFields } from '../../../collection-manager';
|
||||||
import { SetDataLoadingMode } from '../../../modules/blocks/data-blocks/details-multi/setDataLoadingModeSettingsItem';
|
import { SetDataLoadingMode } from '../../../modules/blocks/data-blocks/details-multi/setDataLoadingModeSettingsItem';
|
||||||
import { SetTheCountOfColumnsDisplayedInARow } from '../../../modules/blocks/data-blocks/grid-card/SetTheCountOfColumnsDisplayedInARow';
|
import { SetTheCountOfColumnsDisplayedInARow } from '../../../modules/blocks/data-blocks/grid-card/SetTheCountOfColumnsDisplayedInARow';
|
||||||
import { useRecord } from '../../../record-provider';
|
|
||||||
import {
|
import {
|
||||||
GeneralSchemaDesigner,
|
GeneralSchemaDesigner,
|
||||||
SchemaSettingsDivider,
|
SchemaSettingsDivider,
|
||||||
@ -28,10 +27,11 @@ import {
|
|||||||
import { SchemaSettingsDataScope } from '../../../schema-settings/SchemaSettingsDataScope';
|
import { SchemaSettingsDataScope } from '../../../schema-settings/SchemaSettingsDataScope';
|
||||||
import { SchemaSettingsTemplate } from '../../../schema-settings/SchemaSettingsTemplate';
|
import { SchemaSettingsTemplate } from '../../../schema-settings/SchemaSettingsTemplate';
|
||||||
import { useSchemaTemplate } from '../../../schema-templates';
|
import { useSchemaTemplate } from '../../../schema-templates';
|
||||||
|
import { useBlockTemplateContext } from '../../../schema-templates/BlockTemplateProvider';
|
||||||
import { SchemaComponentOptions } from '../../core';
|
import { SchemaComponentOptions } from '../../core';
|
||||||
import { useDesignable } from '../../hooks';
|
import { useDesignable } from '../../hooks';
|
||||||
import { removeNullCondition } from '../filter';
|
import { removeNullCondition } from '../filter';
|
||||||
import { defaultColumnCount, gridSizes, pageSizeOptions, screenSizeMaps, screenSizeTitleMaps } from './options';
|
import { gridSizes, pageSizeOptions, screenSizeMaps, screenSizeTitleMaps } from './options';
|
||||||
|
|
||||||
export const columnCountMarks = [1, 2, 3, 4, 6, 8, 12, 24].reduce((obj, cur) => {
|
export const columnCountMarks = [1, 2, 3, 4, 6, 8, 12, 24].reduce((obj, cur) => {
|
||||||
obj[cur] = cur;
|
obj[cur] = cur;
|
||||||
@ -47,11 +47,10 @@ export const GridCardDesigner = () => {
|
|||||||
const field = useField();
|
const field = useField();
|
||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
const sortFields = useSortFields(name);
|
const sortFields = useSortFields(name);
|
||||||
const record = useRecord();
|
const { componentNamePrefix } = useBlockTemplateContext();
|
||||||
const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
|
const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
|
||||||
const defaultResource =
|
const defaultResource =
|
||||||
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
const columnCount = field.decoratorProps.columnCount || defaultColumnCount;
|
|
||||||
|
|
||||||
const columnCountSchema = useMemo(() => {
|
const columnCountSchema = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
@ -217,7 +216,11 @@ export const GridCardDesigner = () => {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<SchemaSettingsTemplate componentName={'GridCard'} collectionName={name} resourceName={defaultResource} />
|
<SchemaSettingsTemplate
|
||||||
|
componentName={`${componentNamePrefix}GridCard`}
|
||||||
|
collectionName={name}
|
||||||
|
resourceName={defaultResource}
|
||||||
|
/>
|
||||||
<SchemaSettingsDivider />
|
<SchemaSettingsDivider />
|
||||||
<SchemaSettingsRemove
|
<SchemaSettingsRemove
|
||||||
removeParentsIfNoChildren
|
removeParentsIfNoChildren
|
||||||
|
@ -15,7 +15,6 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useFormBlockContext } from '../../../block-provider/FormBlockProvider';
|
import { useFormBlockContext } from '../../../block-provider/FormBlockProvider';
|
||||||
import { useCollection_deprecated, useSortFields } from '../../../collection-manager';
|
import { useCollection_deprecated, useSortFields } from '../../../collection-manager';
|
||||||
import { SetDataLoadingMode } from '../../../modules/blocks/data-blocks/details-multi/setDataLoadingModeSettingsItem';
|
import { SetDataLoadingMode } from '../../../modules/blocks/data-blocks/details-multi/setDataLoadingModeSettingsItem';
|
||||||
import { useRecord } from '../../../record-provider';
|
|
||||||
import {
|
import {
|
||||||
GeneralSchemaDesigner,
|
GeneralSchemaDesigner,
|
||||||
SchemaSettingsDivider,
|
SchemaSettingsDivider,
|
||||||
@ -27,6 +26,7 @@ import { SchemaSettingsBlockTitleItem } from '../../../schema-settings/SchemaSet
|
|||||||
import { SchemaSettingsDataScope } from '../../../schema-settings/SchemaSettingsDataScope';
|
import { SchemaSettingsDataScope } from '../../../schema-settings/SchemaSettingsDataScope';
|
||||||
import { SchemaSettingsTemplate } from '../../../schema-settings/SchemaSettingsTemplate';
|
import { SchemaSettingsTemplate } from '../../../schema-settings/SchemaSettingsTemplate';
|
||||||
import { useSchemaTemplate } from '../../../schema-templates';
|
import { useSchemaTemplate } from '../../../schema-templates';
|
||||||
|
import { useBlockTemplateContext } from '../../../schema-templates/BlockTemplateProvider';
|
||||||
import { useDesignable } from '../../hooks';
|
import { useDesignable } from '../../hooks';
|
||||||
import { removeNullCondition } from '../filter';
|
import { removeNullCondition } from '../filter';
|
||||||
|
|
||||||
@ -42,7 +42,6 @@ export const ListDesigner = () => {
|
|||||||
const field = useField();
|
const field = useField();
|
||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
const sortFields = useSortFields(name);
|
const sortFields = useSortFields(name);
|
||||||
const record = useRecord();
|
|
||||||
const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
|
const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
|
||||||
const defaultResource =
|
const defaultResource =
|
||||||
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
@ -57,6 +56,7 @@ export const ListDesigner = () => {
|
|||||||
direction: 'asc',
|
direction: 'asc',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
const { componentNamePrefix } = useBlockTemplateContext();
|
||||||
return (
|
return (
|
||||||
<GeneralSchemaDesigner template={template} title={title || name}>
|
<GeneralSchemaDesigner template={template} title={title || name}>
|
||||||
<SchemaSettingsBlockTitleItem />
|
<SchemaSettingsBlockTitleItem />
|
||||||
@ -187,7 +187,11 @@ export const ListDesigner = () => {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<SchemaSettingsTemplate componentName={'List'} collectionName={name} resourceName={defaultResource} />
|
<SchemaSettingsTemplate
|
||||||
|
componentName={`${componentNamePrefix}List`}
|
||||||
|
collectionName={name}
|
||||||
|
resourceName={defaultResource}
|
||||||
|
/>
|
||||||
<SchemaSettingsDivider />
|
<SchemaSettingsDivider />
|
||||||
<SchemaSettingsRemove
|
<SchemaSettingsRemove
|
||||||
removeParentsIfNoChildren
|
removeParentsIfNoChildren
|
||||||
|
@ -11,15 +11,13 @@ import { ArrowLeftOutlined } from '@ant-design/icons';
|
|||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import { useToken } from '../../../style';
|
import { useToken } from '../../../style';
|
||||||
import { useCurrentPopupContext } from './PagePopups';
|
import { useActionContext } from '../action/hooks';
|
||||||
import { usePagePopup } from './pagePopupUtils';
|
|
||||||
|
|
||||||
export const useBackButton = () => {
|
export const useBackButton = () => {
|
||||||
const { params } = useCurrentPopupContext();
|
const { setVisible } = useActionContext();
|
||||||
const { closePopup } = usePagePopup();
|
|
||||||
const goBack = useCallback(() => {
|
const goBack = useCallback(() => {
|
||||||
closePopup(params?.popupuid);
|
setVisible?.(false);
|
||||||
}, [closePopup, params?.popupuid]);
|
}, [setVisible]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
goBack,
|
goBack,
|
||||||
|
@ -13,4 +13,5 @@ export * from './FixedBlockDesignerItem';
|
|||||||
export * from './Page';
|
export * from './Page';
|
||||||
export * from './Page.Settings';
|
export * from './Page.Settings';
|
||||||
export { PagePopups } from './PagePopups';
|
export { PagePopups } from './PagePopups';
|
||||||
|
export { storePopupContext } from './pagePopupUtils';
|
||||||
export * from './PageTab.Settings';
|
export * from './PageTab.Settings';
|
||||||
|
@ -44,6 +44,10 @@ export interface PopupContextStorage extends PopupContext {
|
|||||||
/** used to refresh data for block */
|
/** used to refresh data for block */
|
||||||
service?: any;
|
service?: any;
|
||||||
sourceId?: string;
|
sourceId?: string;
|
||||||
|
/**
|
||||||
|
* if true, will not back to the previous path when closing the popup
|
||||||
|
*/
|
||||||
|
notBackToPreviousPath?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const popupsContextStorage: Record<string, PopupContextStorage> = {};
|
const popupsContextStorage: Record<string, PopupContextStorage> = {};
|
||||||
@ -53,7 +57,10 @@ export const getStoredPopupContext = (popupUid: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to store the context of the current popup when a button is clicked.
|
* Used to store the context of the current popup.
|
||||||
|
*
|
||||||
|
* The context that has already been stored, when displaying the popup,
|
||||||
|
* will directly retrieve the context information from the cache instead of making an API request.
|
||||||
* @param popupUid
|
* @param popupUid
|
||||||
* @param params
|
* @param params
|
||||||
*/
|
*/
|
||||||
@ -108,6 +115,10 @@ export const getPopupPathFromParams = (params: PopupParams) => {
|
|||||||
return `/popups/${popupPath.map((item) => encodePathValue(item)).join('/')}`;
|
return `/popups/${popupPath.map((item) => encodePathValue(item)).join('/')}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: use this hook in a plugin is not recommended
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
export const usePagePopup = () => {
|
export const usePagePopup = () => {
|
||||||
const navigate = useNavigateNoUpdate();
|
const navigate = useNavigateNoUpdate();
|
||||||
const location = useLocationNoUpdate();
|
const location = useLocationNoUpdate();
|
||||||
@ -228,13 +239,13 @@ export const usePagePopup = () => {
|
|||||||
// 1. If there is a value in the cache, it means that the current popup was opened by manual click, so we can simply return to the previous record;
|
// 1. If there is a value in the cache, it means that the current popup was opened by manual click, so we can simply return to the previous record;
|
||||||
// 2. If there is no value in the cache, it means that the current popup was opened by clicking the URL elsewhere, and since there is no history,
|
// 2. If there is no value in the cache, it means that the current popup was opened by clicking the URL elsewhere, and since there is no history,
|
||||||
// we need to construct the URL of the previous record to return to;
|
// we need to construct the URL of the previous record to return to;
|
||||||
if (getStoredPopupContext(currentPopupUid)) {
|
if (getStoredPopupContext(currentPopupUid) && !getStoredPopupContext(currentPopupUid).notBackToPreviousPath) {
|
||||||
navigate(-1);
|
navigate(-1);
|
||||||
} else {
|
} else {
|
||||||
navigate(withSearchParams(removeLastPopupPath(location.pathname)));
|
navigate(withSearchParams(removeLastPopupPath(location.pathname)));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[navigate, location, isPopupVisibleControlledByURL],
|
[isPopupVisibleControlledByURL, setVisibleFromAction, navigate, location?.pathname],
|
||||||
);
|
);
|
||||||
|
|
||||||
const changeTab = useCallback(
|
const changeTab = useCallback(
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
import { ArrayField } from '@formily/core';
|
import { ArrayField } from '@formily/core';
|
||||||
import { RecursionField, useField, useFieldSchema } from '@formily/react';
|
import { RecursionField, useField, useFieldSchema } from '@formily/react';
|
||||||
|
import { toArr } from '@formily/shared';
|
||||||
import { Select } from 'antd';
|
import { Select } from 'antd';
|
||||||
import { differenceBy, unionBy } from 'lodash';
|
import { differenceBy, unionBy } from 'lodash';
|
||||||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||||
@ -20,10 +21,9 @@ import { CollectionProvider_deprecated, useCollection_deprecated } from '../../.
|
|||||||
import { FormProvider, SchemaComponentOptions } from '../../core';
|
import { FormProvider, SchemaComponentOptions } from '../../core';
|
||||||
import { useCompile } from '../../hooks';
|
import { useCompile } from '../../hooks';
|
||||||
import { ActionContextProvider, useActionContext } from '../action';
|
import { ActionContextProvider, useActionContext } from '../action';
|
||||||
|
import { Upload } from '../upload';
|
||||||
import { useFieldNames } from './useFieldNames';
|
import { useFieldNames } from './useFieldNames';
|
||||||
import { getLabelFormatValue, useLabelUiSchema } from './util';
|
import { getLabelFormatValue, useLabelUiSchema } from './util';
|
||||||
import { Upload } from '../upload';
|
|
||||||
import { toArr } from '@formily/shared';
|
|
||||||
|
|
||||||
export const RecordPickerContext = createContext(null);
|
export const RecordPickerContext = createContext(null);
|
||||||
RecordPickerContext.displayName = 'RecordPickerContext';
|
RecordPickerContext.displayName = 'RecordPickerContext';
|
||||||
@ -148,11 +148,6 @@ export const InputRecordPicker: React.FC<any> = (props: IRecordPickerProps) => {
|
|||||||
return Array.isArray(value) ? value?.map((v) => v[fieldNames.value]) : value?.[fieldNames.value];
|
return Array.isArray(value) ? value?.map((v) => v[fieldNames.value]) : value?.[fieldNames.value];
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelect = () => {
|
|
||||||
setVisible(true);
|
|
||||||
setSelectedRows([]);
|
|
||||||
};
|
|
||||||
|
|
||||||
// const handleRemove = (file) => {
|
// const handleRemove = (file) => {
|
||||||
// const newOptions = options.filter((option) => option.id !== file.id);
|
// const newOptions = options.filter((option) => option.id !== file.id);
|
||||||
// setOptions(newOptions);
|
// setOptions(newOptions);
|
||||||
|
@ -35,6 +35,7 @@ import { SchemaSettingsConnectDataBlocks } from '../../../schema-settings/Schema
|
|||||||
import { SchemaSettingsDataScope } from '../../../schema-settings/SchemaSettingsDataScope';
|
import { SchemaSettingsDataScope } from '../../../schema-settings/SchemaSettingsDataScope';
|
||||||
import { SchemaSettingsTemplate } from '../../../schema-settings/SchemaSettingsTemplate';
|
import { SchemaSettingsTemplate } from '../../../schema-settings/SchemaSettingsTemplate';
|
||||||
import { useSchemaTemplate } from '../../../schema-templates';
|
import { useSchemaTemplate } from '../../../schema-templates';
|
||||||
|
import { useBlockTemplateContext } from '../../../schema-templates/BlockTemplateProvider';
|
||||||
import { useDesignable } from '../../hooks';
|
import { useDesignable } from '../../hooks';
|
||||||
import { removeNullCondition } from '../filter';
|
import { removeNullCondition } from '../filter';
|
||||||
|
|
||||||
@ -86,6 +87,7 @@ export const TableBlockDesigner = () => {
|
|||||||
const { service } = useTableBlockContext();
|
const { service } = useTableBlockContext();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
|
const { componentNamePrefix } = useBlockTemplateContext();
|
||||||
|
|
||||||
const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
|
const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
|
||||||
const defaultResource =
|
const defaultResource =
|
||||||
@ -304,7 +306,11 @@ export const TableBlockDesigner = () => {
|
|||||||
<SchemaSettingsConnectDataBlocks type={FilterBlockType.TABLE} emptyDescription={t('No blocks to connect')} />
|
<SchemaSettingsConnectDataBlocks type={FilterBlockType.TABLE} emptyDescription={t('No blocks to connect')} />
|
||||||
{supportTemplate && <SchemaSettingsDivider />}
|
{supportTemplate && <SchemaSettingsDivider />}
|
||||||
{supportTemplate && (
|
{supportTemplate && (
|
||||||
<SchemaSettingsTemplate componentName={'Table'} collectionName={name} resourceName={defaultResource} />
|
<SchemaSettingsTemplate
|
||||||
|
componentName={`${componentNamePrefix}Table`}
|
||||||
|
collectionName={name}
|
||||||
|
resourceName={defaultResource}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<SchemaSettingsDivider />
|
<SchemaSettingsDivider />
|
||||||
<SchemaSettingsRemove
|
<SchemaSettingsRemove
|
||||||
|
@ -23,6 +23,7 @@ import {
|
|||||||
} from '../../../schema-settings';
|
} from '../../../schema-settings';
|
||||||
import { SchemaSettingsTemplate } from '../../../schema-settings/SchemaSettingsTemplate';
|
import { SchemaSettingsTemplate } from '../../../schema-settings/SchemaSettingsTemplate';
|
||||||
import { useSchemaTemplate } from '../../../schema-templates';
|
import { useSchemaTemplate } from '../../../schema-templates';
|
||||||
|
import { useBlockTemplateContext } from '../../../schema-templates/BlockTemplateProvider';
|
||||||
import { useDesignable } from '../../hooks';
|
import { useDesignable } from '../../hooks';
|
||||||
|
|
||||||
export const TableVoidDesigner = () => {
|
export const TableVoidDesigner = () => {
|
||||||
@ -48,6 +49,7 @@ export const TableVoidDesigner = () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
const template = useSchemaTemplate();
|
const template = useSchemaTemplate();
|
||||||
|
const { componentNamePrefix } = useBlockTemplateContext();
|
||||||
return (
|
return (
|
||||||
<GeneralSchemaDesigner template={template} title={title || name}>
|
<GeneralSchemaDesigner template={template} title={title || name}>
|
||||||
<SchemaSettingsSwitchItem
|
<SchemaSettingsSwitchItem
|
||||||
@ -213,7 +215,7 @@ export const TableVoidDesigner = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<SchemaSettingsDivider />
|
<SchemaSettingsDivider />
|
||||||
<SchemaSettingsTemplate componentName={'Table'} collectionName={name} />
|
<SchemaSettingsTemplate componentName={`${componentNamePrefix}Table`} collectionName={name} />
|
||||||
<SchemaSettingsDivider />
|
<SchemaSettingsDivider />
|
||||||
<SchemaSettingsRemove
|
<SchemaSettingsRemove
|
||||||
removeParentsIfNoChildren
|
removeParentsIfNoChildren
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
import { Schema } from '@formily/react';
|
import { Schema } from '@formily/react';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
|
useActionAvailable,
|
||||||
useCollection,
|
useCollection,
|
||||||
useCollectionManager_deprecated,
|
useCollectionManager_deprecated,
|
||||||
useCollection_deprecated,
|
useCollection_deprecated,
|
||||||
@ -22,7 +23,6 @@ import {
|
|||||||
useCreateEditFormBlock,
|
useCreateEditFormBlock,
|
||||||
useCreateFormBlock,
|
useCreateFormBlock,
|
||||||
useCreateTableBlock,
|
useCreateTableBlock,
|
||||||
useActionAvailable,
|
|
||||||
} from '../..';
|
} from '../..';
|
||||||
import { CompatibleSchemaInitializer } from '../../application/schema-initializer/CompatibleSchemaInitializer';
|
import { CompatibleSchemaInitializer } from '../../application/schema-initializer/CompatibleSchemaInitializer';
|
||||||
import { useCreateDetailsBlock } from '../../modules/blocks/data-blocks/details-multi/DetailsBlockInitializer';
|
import { useCreateDetailsBlock } from '../../modules/blocks/data-blocks/details-multi/DetailsBlockInitializer';
|
||||||
@ -101,7 +101,7 @@ function useRecordBlocks() {
|
|||||||
},
|
},
|
||||||
onlyCurrentDataSource: true,
|
onlyCurrentDataSource: true,
|
||||||
hideSearch: true,
|
hideSearch: true,
|
||||||
componentType: 'ReadPrettyFormItem',
|
componentType: `ReadPrettyFormItem`,
|
||||||
createBlockSchema,
|
createBlockSchema,
|
||||||
templateWrap: useCallback(
|
templateWrap: useCallback(
|
||||||
(templateSchema, { item }) => {
|
(templateSchema, { item }) => {
|
||||||
@ -140,7 +140,7 @@ function useRecordBlocks() {
|
|||||||
onlyCurrentDataSource: true,
|
onlyCurrentDataSource: true,
|
||||||
hideSearch: true,
|
hideSearch: true,
|
||||||
hideOtherRecordsInPopup: true,
|
hideOtherRecordsInPopup: true,
|
||||||
componentType: 'FormItem',
|
componentType: `FormItem`,
|
||||||
createBlockSchema: createEditFormBlock,
|
createBlockSchema: createEditFormBlock,
|
||||||
templateWrap: templateWrapEdit,
|
templateWrap: templateWrapEdit,
|
||||||
showAssociationFields: true,
|
showAssociationFields: true,
|
||||||
@ -166,7 +166,7 @@ function useRecordBlocks() {
|
|||||||
},
|
},
|
||||||
onlyCurrentDataSource: true,
|
onlyCurrentDataSource: true,
|
||||||
hideSearch: true,
|
hideSearch: true,
|
||||||
componentType: 'FormItem',
|
componentType: `FormItem`,
|
||||||
createBlockSchema: ({ item, fromOthersInPopup }) => {
|
createBlockSchema: ({ item, fromOthersInPopup }) => {
|
||||||
if (fromOthersInPopup) {
|
if (fromOthersInPopup) {
|
||||||
return createFormBlock({ item, fromOthersInPopup });
|
return createFormBlock({ item, fromOthersInPopup });
|
||||||
|
@ -81,6 +81,7 @@ const TabPaneInitializers = (props?: any) => {
|
|||||||
'x-component': 'Action.Modal',
|
'x-component': 'Action.Modal',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
width: 520,
|
width: 520,
|
||||||
|
zIndex: 2000,
|
||||||
},
|
},
|
||||||
type: 'void',
|
type: 'void',
|
||||||
title: '{{t("Add tab")}}',
|
title: '{{t("Add tab")}}',
|
||||||
|
@ -271,7 +271,7 @@ function FinallyButton({
|
|||||||
}),
|
}),
|
||||||
React.cloneElement(rightButton as React.ReactElement<any, string>, {
|
React.cloneElement(rightButton as React.ReactElement<any, string>, {
|
||||||
loading: false,
|
loading: false,
|
||||||
style: props?.style,
|
style: { ...props?.style, justifyContent: 'center' },
|
||||||
}),
|
}),
|
||||||
]}
|
]}
|
||||||
menu={menu}
|
menu={menu}
|
||||||
|
@ -30,6 +30,7 @@ import { useDataSourceManager } from '../data-source/data-source/DataSourceManag
|
|||||||
import { isAssocField } from '../filter-provider/utils';
|
import { isAssocField } from '../filter-provider/utils';
|
||||||
import { useActionContext, useCompile, useDesignable } from '../schema-component';
|
import { useActionContext, useCompile, useDesignable } from '../schema-component';
|
||||||
import { useSchemaTemplateManager } from '../schema-templates';
|
import { useSchemaTemplateManager } from '../schema-templates';
|
||||||
|
import { useBlockTemplateContext } from '../schema-templates/BlockTemplateProvider';
|
||||||
|
|
||||||
export const itemsMerge = (items1) => {
|
export const itemsMerge = (items1) => {
|
||||||
return items1;
|
return items1;
|
||||||
@ -879,11 +880,17 @@ export const useCollectionDataSourceItems = ({
|
|||||||
currentText?: string;
|
currentText?: string;
|
||||||
otherText?: string;
|
otherText?: string;
|
||||||
}) => {
|
}) => {
|
||||||
|
const { componentNamePrefix } = useBlockTemplateContext();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dm = useDataSourceManager();
|
const dm = useDataSourceManager();
|
||||||
const dataSourceKey = useDataSourceKey();
|
const dataSourceKey = useDataSourceKey();
|
||||||
const collection = useCollection();
|
const collection = useCollection();
|
||||||
const associationFields = useAssociationFields({ componentName, filterCollections: filter, showAssociationFields });
|
const associationFields = useAssociationFields({
|
||||||
|
componentName: componentNamePrefix + componentName,
|
||||||
|
filterCollections: filter,
|
||||||
|
showAssociationFields,
|
||||||
|
componentNamePrefix,
|
||||||
|
});
|
||||||
const association = useAssociationName();
|
const association = useAssociationName();
|
||||||
|
|
||||||
let allCollections = dm.getAllCollections({
|
let allCollections = dm.getAllCollections({
|
||||||
@ -911,11 +918,12 @@ export const useCollectionDataSourceItems = ({
|
|||||||
name,
|
name,
|
||||||
association,
|
association,
|
||||||
collections,
|
collections,
|
||||||
componentName,
|
componentName: componentNamePrefix + componentName,
|
||||||
searchValue: '',
|
searchValue: '',
|
||||||
dataSource: key,
|
dataSource: key,
|
||||||
getTemplatesByCollection,
|
getTemplatesByCollection,
|
||||||
t,
|
t,
|
||||||
|
componentNamePrefix,
|
||||||
}).sort((item) => {
|
}).sort((item) => {
|
||||||
// fix https://nocobase.height.app/T-3551
|
// fix https://nocobase.height.app/T-3551
|
||||||
const inherits = _.toArray(collection?.inherits || []);
|
const inherits = _.toArray(collection?.inherits || []);
|
||||||
@ -1401,6 +1409,7 @@ const getChildren = ({
|
|||||||
searchValue,
|
searchValue,
|
||||||
getTemplatesByCollection,
|
getTemplatesByCollection,
|
||||||
t,
|
t,
|
||||||
|
componentNamePrefix,
|
||||||
}: {
|
}: {
|
||||||
name: string;
|
name: string;
|
||||||
association: string;
|
association: string;
|
||||||
@ -1409,7 +1418,8 @@ const getChildren = ({
|
|||||||
searchValue: string;
|
searchValue: string;
|
||||||
dataSource: string;
|
dataSource: string;
|
||||||
getTemplatesByCollection: (dataSource: string, collectionName: string, resourceName?: string) => any;
|
getTemplatesByCollection: (dataSource: string, collectionName: string, resourceName?: string) => any;
|
||||||
t;
|
t: any;
|
||||||
|
componentNamePrefix: string;
|
||||||
}) => {
|
}) => {
|
||||||
return collections
|
return collections
|
||||||
?.filter((item) => {
|
?.filter((item) => {
|
||||||
@ -1419,11 +1429,16 @@ const getChildren = ({
|
|||||||
if (!item.filterTargetKey) {
|
if (!item.filterTargetKey) {
|
||||||
return false;
|
return false;
|
||||||
} else if (
|
} else if (
|
||||||
['Kanban', 'FormItem'].includes(componentName) &&
|
[componentNamePrefix + 'Kanban', componentNamePrefix + 'FormItem'].includes(componentName) &&
|
||||||
((item.template === 'view' && !item.writableView) || item.template === 'sql')
|
((item.template === 'view' && !item.writableView) || item.template === 'sql')
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
} else if (item.template === 'file' && ['Kanban', 'FormItem', 'Calendar'].includes(componentName)) {
|
} else if (
|
||||||
|
item.template === 'file' &&
|
||||||
|
[componentNamePrefix + 'Kanban', componentNamePrefix + 'FormItem', componentNamePrefix + 'Calendar'].includes(
|
||||||
|
componentName,
|
||||||
|
)
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
const title = item.title || item.tableName;
|
const title = item.title || item.tableName;
|
||||||
@ -1483,7 +1498,10 @@ const getChildren = ({
|
|||||||
dataSource,
|
dataSource,
|
||||||
title: t('Duplicate template'),
|
title: t('Duplicate template'),
|
||||||
children: templates.map((template) => {
|
children: templates.map((template) => {
|
||||||
const templateName = ['FormItem', 'ReadPrettyFormItem'].includes(template?.componentName)
|
const templateName = [
|
||||||
|
componentNamePrefix + 'FormItem',
|
||||||
|
componentNamePrefix + 'ReadPrettyFormItem',
|
||||||
|
].includes(template?.componentName)
|
||||||
? `${template?.name} ${t('(Fields only)')}`
|
? `${template?.name} ${t('(Fields only)')}`
|
||||||
: template?.name;
|
: template?.name;
|
||||||
return {
|
return {
|
||||||
@ -1503,7 +1521,10 @@ const getChildren = ({
|
|||||||
dataSource,
|
dataSource,
|
||||||
title: t('Reference template'),
|
title: t('Reference template'),
|
||||||
children: templates.map((template) => {
|
children: templates.map((template) => {
|
||||||
const templateName = ['FormItem', 'ReadPrettyFormItem'].includes(template?.componentName)
|
const templateName = [
|
||||||
|
componentNamePrefix + 'FormItem',
|
||||||
|
componentNamePrefix + 'ReadPrettyFormItem',
|
||||||
|
].includes(template?.componentName)
|
||||||
? `${template?.name} ${t('(Fields only)')}`
|
? `${template?.name} ${t('(Fields only)')}`
|
||||||
: template?.name;
|
: template?.name;
|
||||||
return {
|
return {
|
||||||
@ -1525,9 +1546,11 @@ function useAssociationFields({
|
|||||||
componentName,
|
componentName,
|
||||||
filterCollections,
|
filterCollections,
|
||||||
showAssociationFields,
|
showAssociationFields,
|
||||||
|
componentNamePrefix,
|
||||||
}: {
|
}: {
|
||||||
componentName: string;
|
componentName: string;
|
||||||
filterCollections: (options: { collection?: Collection; associationField?: CollectionFieldOptions }) => boolean;
|
filterCollections: (options: { collection?: Collection; associationField?: CollectionFieldOptions }) => boolean;
|
||||||
|
componentNamePrefix: string;
|
||||||
showAssociationFields?: boolean;
|
showAssociationFields?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
@ -1566,11 +1589,11 @@ function useAssociationFields({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 针对弹窗中的详情区块
|
// 针对弹窗中的详情区块
|
||||||
if (componentName === 'ReadPrettyFormItem') {
|
if (componentName === componentNamePrefix + 'ReadPrettyFormItem') {
|
||||||
if (['hasOne', 'belongsTo'].includes(field.type)) {
|
if (['hasOne', 'belongsTo'].includes(field.type)) {
|
||||||
return template.componentName === 'ReadPrettyFormItem';
|
return template.componentName === componentNamePrefix + 'ReadPrettyFormItem';
|
||||||
} else {
|
} else {
|
||||||
return template.componentName === 'Details';
|
return template.componentName === componentNamePrefix + 'Details';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1611,7 +1634,10 @@ function useAssociationFields({
|
|||||||
dataSource,
|
dataSource,
|
||||||
title: t('Duplicate template'),
|
title: t('Duplicate template'),
|
||||||
children: templates.map((template) => {
|
children: templates.map((template) => {
|
||||||
const templateName = ['FormItem', 'ReadPrettyFormItem'].includes(template?.componentName)
|
const templateName = [
|
||||||
|
componentNamePrefix + 'FormItem',
|
||||||
|
componentNamePrefix + 'ReadPrettyFormItem',
|
||||||
|
].includes(template?.componentName)
|
||||||
? `${template?.name} ${t('(Fields only)')}`
|
? `${template?.name} ${t('(Fields only)')}`
|
||||||
: template?.name;
|
: template?.name;
|
||||||
return {
|
return {
|
||||||
@ -1633,7 +1659,10 @@ function useAssociationFields({
|
|||||||
dataSource,
|
dataSource,
|
||||||
title: t('Reference template'),
|
title: t('Reference template'),
|
||||||
children: templates.map((template) => {
|
children: templates.map((template) => {
|
||||||
const templateName = ['FormItem', 'ReadPrettyFormItem'].includes(template?.componentName)
|
const templateName = [
|
||||||
|
componentNamePrefix + 'FormItem',
|
||||||
|
componentNamePrefix + 'ReadPrettyFormItem',
|
||||||
|
].includes(template?.componentName)
|
||||||
? `${template?.name} ${t('(Fields only)')}`
|
? `${template?.name} ${t('(Fields only)')}`
|
||||||
: template?.name;
|
: template?.name;
|
||||||
return {
|
return {
|
||||||
@ -1662,5 +1691,6 @@ function useAssociationFields({
|
|||||||
getTemplatesByCollection,
|
getTemplatesByCollection,
|
||||||
showAssociationFields,
|
showAssociationFields,
|
||||||
t,
|
t,
|
||||||
|
componentNamePrefix,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@ import { SchemaComponentOptions } from '../schema-component/core/SchemaComponent
|
|||||||
import { useCompile } from '../schema-component/hooks/useCompile';
|
import { useCompile } from '../schema-component/hooks/useCompile';
|
||||||
import { Designable, createDesignable, useDesignable } from '../schema-component/hooks/useDesignable';
|
import { Designable, createDesignable, useDesignable } from '../schema-component/hooks/useDesignable';
|
||||||
import { useSchemaTemplateManager } from '../schema-templates';
|
import { useSchemaTemplateManager } from '../schema-templates';
|
||||||
import { useBlockTemplateContext } from '../schema-templates/BlockTemplate';
|
import { useBlockTemplateContext } from '../schema-templates/BlockTemplateProvider';
|
||||||
import { useLocalVariables, useVariables } from '../variables';
|
import { useLocalVariables, useVariables } from '../variables';
|
||||||
import { FormDataTemplates } from './DataTemplates';
|
import { FormDataTemplates } from './DataTemplates';
|
||||||
import { EnableChildCollections } from './EnableChildCollections';
|
import { EnableChildCollections } from './EnableChildCollections';
|
||||||
|
@ -20,7 +20,7 @@ import { SchemaComponent } from '../schema-component/core/SchemaComponent';
|
|||||||
import { useCompile } from '../schema-component/hooks/useCompile';
|
import { useCompile } from '../schema-component/hooks/useCompile';
|
||||||
import { createDesignable } from '../schema-component/hooks/useDesignable';
|
import { createDesignable } from '../schema-component/hooks/useDesignable';
|
||||||
import { useSchemaTemplateManager } from '../schema-templates';
|
import { useSchemaTemplateManager } from '../schema-templates';
|
||||||
import { useBlockTemplateContext } from '../schema-templates/BlockTemplate';
|
import { useBlockTemplateContext } from '../schema-templates/BlockTemplateProvider';
|
||||||
import { SchemaSettingsItem, useSchemaSettings } from './SchemaSettings';
|
import { SchemaSettingsItem, useSchemaSettings } from './SchemaSettings';
|
||||||
|
|
||||||
export function SchemaSettingsTemplate(props) {
|
export function SchemaSettingsTemplate(props) {
|
||||||
|
@ -8,17 +8,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { observer, useField, useFieldSchema } from '@formily/react';
|
import { observer, useField, useFieldSchema } from '@formily/react';
|
||||||
import React, { createContext, useContext, useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { CollectionDeletedPlaceholder, RemoteSchemaComponent, useDesignable } from '..';
|
import { BlockTemplateProvider, CollectionDeletedPlaceholder, RemoteSchemaComponent, useDesignable } from '..';
|
||||||
import { useSchemaTemplateManager } from './SchemaTemplateManagerProvider';
|
|
||||||
import { useTemplateBlockContext } from '../block-provider/TemplateBlockProvider';
|
import { useTemplateBlockContext } from '../block-provider/TemplateBlockProvider';
|
||||||
|
import { useSchemaTemplateManager } from './SchemaTemplateManagerProvider';
|
||||||
const BlockTemplateContext = createContext<any>({});
|
|
||||||
BlockTemplateContext.displayName = 'BlockTemplateContext';
|
|
||||||
|
|
||||||
export const useBlockTemplateContext = () => {
|
|
||||||
return useContext(BlockTemplateContext);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const BlockTemplate = observer(
|
export const BlockTemplate = observer(
|
||||||
(props: any) => {
|
(props: any) => {
|
||||||
@ -36,9 +29,9 @@ export const BlockTemplate = observer(
|
|||||||
onTemplateSuccess?.();
|
onTemplateSuccess?.();
|
||||||
};
|
};
|
||||||
return template ? (
|
return template ? (
|
||||||
<BlockTemplateContext.Provider value={{ dn, field, fieldSchema, template }}>
|
<BlockTemplateProvider {...{ dn, field, fieldSchema, template }}>
|
||||||
<RemoteSchemaComponent noForm uid={template?.uid} onSuccess={onSuccess} />
|
<RemoteSchemaComponent noForm uid={template?.uid} onSuccess={onSuccess} />
|
||||||
</BlockTemplateContext.Provider>
|
</BlockTemplateProvider>
|
||||||
) : (
|
) : (
|
||||||
<CollectionDeletedPlaceholder type="Block template" name={templateId} />
|
<CollectionDeletedPlaceholder type="Block template" name={templateId} />
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* 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 { ISchema } from '@formily/json-schema';
|
||||||
|
import React, { createContext, FC, useContext } from 'react';
|
||||||
|
|
||||||
|
interface BlockTemplateProviderProps {
|
||||||
|
/**
|
||||||
|
* 为模板中的 componentName 参数设置一个前缀,用于实现相同区块的模板在不同的上下文中不会互相引用
|
||||||
|
*/
|
||||||
|
componentNamePrefix?: string;
|
||||||
|
dn?: any;
|
||||||
|
field?: any;
|
||||||
|
fieldSchema?: ISchema;
|
||||||
|
template?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BlockTemplateContext = createContext<BlockTemplateProviderProps>({ componentNamePrefix: '' });
|
||||||
|
|
||||||
|
export const BlockTemplateProvider: FC<BlockTemplateProviderProps> = (props) => {
|
||||||
|
return (
|
||||||
|
<BlockTemplateContext.Provider value={{ ...props, componentNamePrefix: props.componentNamePrefix || '' }}>
|
||||||
|
{props.children}
|
||||||
|
</BlockTemplateContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const useBlockTemplateContext = () => {
|
||||||
|
return useContext(BlockTemplateContext);
|
||||||
|
};
|
@ -9,4 +9,5 @@
|
|||||||
|
|
||||||
export * from './BlockTemplateDetails';
|
export * from './BlockTemplateDetails';
|
||||||
export * from './BlockTemplatePage';
|
export * from './BlockTemplatePage';
|
||||||
|
export * from './BlockTemplateProvider';
|
||||||
export * from './SchemaTemplateManagerProvider';
|
export * from './SchemaTemplateManagerProvider';
|
||||||
|
@ -24,9 +24,9 @@ export * from './number';
|
|||||||
export * from './parse-filter';
|
export * from './parse-filter';
|
||||||
export * from './registry';
|
export * from './registry';
|
||||||
// export * from './toposort';
|
// export * from './toposort';
|
||||||
|
export * from './i18n';
|
||||||
export * from './isPortalInBody';
|
export * from './isPortalInBody';
|
||||||
|
export * from './parseHTML';
|
||||||
export * from './uid';
|
export * from './uid';
|
||||||
export * from './url';
|
export * from './url';
|
||||||
export { dayjs, lodash };
|
export { dayjs, lodash };
|
||||||
export * from './parseHTML';
|
|
||||||
export * from './i18n';
|
|
||||||
|
@ -70,7 +70,7 @@ test('menu permission ', async ({ page, mockPage, mockRole, updateRole }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('i18n should not fallbackNS', async ({ page }) => {
|
test('i18n should not fallbackNS', async ({ page }) => {
|
||||||
await page.goto('/admin/settings/system-settings');
|
await page.goto('/');
|
||||||
|
|
||||||
// 创建 Users 页面
|
// 创建 Users 页面
|
||||||
await page.getByTestId('schema-initializer-Menu-header').hover();
|
await page.getByTestId('schema-initializer-Menu-header').hover();
|
||||||
@ -78,11 +78,12 @@ test('i18n should not fallbackNS', async ({ page }) => {
|
|||||||
await page.getByLabel('block-item-Input-Menu item').getByRole('textbox').click();
|
await page.getByLabel('block-item-Input-Menu item').getByRole('textbox').click();
|
||||||
await page.getByLabel('block-item-Input-Menu item').getByRole('textbox').fill('Users');
|
await page.getByLabel('block-item-Input-Menu item').getByRole('textbox').fill('Users');
|
||||||
await page.getByRole('button', { name: 'OK' }).click();
|
await page.getByRole('button', { name: 'OK' }).click();
|
||||||
await expect(page.getByLabel('Users')).toBeVisible();
|
await page.getByLabel('Users').first().click();
|
||||||
|
await expect(page.getByLabel('Users').first()).toBeVisible();
|
||||||
await expect(page.getByLabel('用户')).not.toBeVisible();
|
await expect(page.getByLabel('用户')).not.toBeVisible();
|
||||||
|
|
||||||
// 添加中文选项
|
// 添加中文选项
|
||||||
await page.reload();
|
await page.goto('/admin/settings/system-settings');
|
||||||
await page.getByTestId('select-multiple').click();
|
await page.getByTestId('select-multiple').click();
|
||||||
await page.getByRole('option', { name: '简体中文 (zh-CN)' }).click();
|
await page.getByRole('option', { name: '简体中文 (zh-CN)' }).click();
|
||||||
await page.getByLabel('action-Action-Submit').click();
|
await page.getByLabel('action-Action-Submit').click();
|
||||||
@ -92,19 +93,18 @@ test('i18n should not fallbackNS', async ({ page }) => {
|
|||||||
await page.getByText('LanguageEnglish').click();
|
await page.getByText('LanguageEnglish').click();
|
||||||
await page.getByRole('option', { name: '简体中文' }).click();
|
await page.getByRole('option', { name: '简体中文' }).click();
|
||||||
|
|
||||||
// await page.reload();
|
|
||||||
|
|
||||||
// 应该显示 Users 而非中文 “用户”
|
// 应该显示 Users 而非中文 “用户”
|
||||||
await expect(page.getByLabel('Users')).toBeVisible();
|
await expect(page.getByLabel('Users').first()).toBeVisible();
|
||||||
await expect(page.getByLabel('用户')).not.toBeVisible();
|
await expect(page.getByLabel('用户')).not.toBeVisible();
|
||||||
|
|
||||||
// 删除中文
|
// 删除中文
|
||||||
|
await page.goto('/admin/settings/system-settings');
|
||||||
await page.getByLabel('简体中文 (zh-CN)').getByLabel('icon-close-tag').click();
|
await page.getByLabel('简体中文 (zh-CN)').getByLabel('icon-close-tag').click();
|
||||||
await page.getByLabel('action-Action-提交').click();
|
await page.getByLabel('action-Action-提交').click();
|
||||||
|
|
||||||
// 删除 Users 页面
|
// 删除 Users 页面
|
||||||
await page.getByLabel('Users').hover();
|
await page.getByLabel('Users').first().hover();
|
||||||
await page.getByLabel('designer-schema-settings-Menu').hover();
|
await page.getByLabel('designer-schema-settings-Menu').first().hover();
|
||||||
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
||||||
await page.getByRole('button', { name: 'OK' }).click();
|
await page.getByRole('button', { name: 'OK' }).click();
|
||||||
});
|
});
|
||||||
|
@ -7,10 +7,12 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { BlockInitializer, useSchemaInitializerItem } from '@nocobase/client';
|
import { BlockInitializer, useOpenModeContext, useSchemaInitializerItem } from '@nocobase/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
export const BulkEditActionInitializer = () => {
|
export const BulkEditActionInitializer = () => {
|
||||||
|
const { defaultOpenMode } = useOpenModeContext();
|
||||||
|
|
||||||
const schema = {
|
const schema = {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
title: '{{t("Bulk edit")}}',
|
title: '{{t("Bulk edit")}}',
|
||||||
@ -20,7 +22,7 @@ export const BulkEditActionInitializer = () => {
|
|||||||
updateMode: 'selected',
|
updateMode: 'selected',
|
||||||
},
|
},
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
openMode: 'drawer',
|
openMode: defaultOpenMode,
|
||||||
icon: 'EditOutlined',
|
icon: 'EditOutlined',
|
||||||
},
|
},
|
||||||
properties: {
|
properties: {
|
||||||
|
@ -7,10 +7,12 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ActionInitializerItem } from '@nocobase/client';
|
import { ActionInitializerItem, useOpenModeContext } from '@nocobase/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
export const DuplicateActionInitializer = (props) => {
|
export const DuplicateActionInitializer = (props) => {
|
||||||
|
const { defaultOpenMode } = useOpenModeContext();
|
||||||
|
|
||||||
const schema = {
|
const schema = {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
'x-action': 'duplicate',
|
'x-action': 'duplicate',
|
||||||
@ -19,7 +21,7 @@ export const DuplicateActionInitializer = (props) => {
|
|||||||
'x-component': 'Action.Link',
|
'x-component': 'Action.Link',
|
||||||
'x-decorator': 'ACLActionProvider',
|
'x-decorator': 'ACLActionProvider',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
openMode: 'drawer',
|
openMode: defaultOpenMode,
|
||||||
component: 'DuplicateAction',
|
component: 'DuplicateAction',
|
||||||
},
|
},
|
||||||
properties: {
|
properties: {
|
||||||
|
@ -72,7 +72,7 @@ describe('createCalendarBlockSchema', () => {
|
|||||||
},
|
},
|
||||||
"title": "{{t('View record', { ns: 'calendar' })}}",
|
"title": "{{t('View record', { ns: 'calendar' })}}",
|
||||||
"type": "void",
|
"type": "void",
|
||||||
"x-component": "Action.Drawer",
|
"x-component": "Action.Container",
|
||||||
"x-component-props": {
|
"x-component-props": {
|
||||||
"className": "nb-action-popup",
|
"className": "nb-action-popup",
|
||||||
},
|
},
|
||||||
|
@ -18,6 +18,7 @@ import {
|
|||||||
SchemaSettingsSwitchItem,
|
SchemaSettingsSwitchItem,
|
||||||
SchemaSettingsTemplate,
|
SchemaSettingsTemplate,
|
||||||
removeNullCondition,
|
removeNullCondition,
|
||||||
|
useBlockTemplateContext,
|
||||||
useCollection,
|
useCollection,
|
||||||
useCollectionManager_deprecated,
|
useCollectionManager_deprecated,
|
||||||
useDesignable,
|
useDesignable,
|
||||||
@ -212,10 +213,11 @@ export const calendarBlockSettings = new SchemaSettings({
|
|||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection();
|
const { name } = useCollection();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { componentNamePrefix } = useBlockTemplateContext();
|
||||||
const defaultResource =
|
const defaultResource =
|
||||||
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
return {
|
return {
|
||||||
componentName: 'Calendar',
|
componentName: `${componentNamePrefix}Calendar`,
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
resourceName: defaultResource,
|
resourceName: defaultResource,
|
||||||
};
|
};
|
||||||
|
@ -62,7 +62,7 @@ export const createCalendarBlockUISchema = (options: {
|
|||||||
properties: {
|
properties: {
|
||||||
drawer: {
|
drawer: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
'x-component': 'Action.Drawer',
|
'x-component': 'Action.Container',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
className: 'nb-action-popup',
|
className: 'nb-action-popup',
|
||||||
},
|
},
|
||||||
|
@ -45,7 +45,7 @@ export const CalendarBlockInitializer = ({
|
|||||||
return (
|
return (
|
||||||
<DataBlockInitializer
|
<DataBlockInitializer
|
||||||
{...itemConfig}
|
{...itemConfig}
|
||||||
componentType={'Calendar'}
|
componentType={`Calendar`}
|
||||||
icon={<FormOutlined />}
|
icon={<FormOutlined />}
|
||||||
onCreateBlockSchema={async (options) => {
|
onCreateBlockSchema={async (options) => {
|
||||||
if (createBlockSchema) {
|
if (createBlockSchema) {
|
||||||
|
@ -16,6 +16,7 @@ import {
|
|||||||
SchemaSettingsTemplate,
|
SchemaSettingsTemplate,
|
||||||
removeNullCondition,
|
removeNullCondition,
|
||||||
setDataLoadingModeSettingsItem,
|
setDataLoadingModeSettingsItem,
|
||||||
|
useBlockTemplateContext,
|
||||||
useCollection,
|
useCollection,
|
||||||
useCollection_deprecated,
|
useCollection_deprecated,
|
||||||
useCompile,
|
useCompile,
|
||||||
@ -245,8 +246,9 @@ export const oldGanttSettings = new SchemaSettings({
|
|||||||
Component: SchemaSettingsTemplate,
|
Component: SchemaSettingsTemplate,
|
||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { name } = useCollection_deprecated();
|
||||||
|
const { componentNamePrefix } = useBlockTemplateContext();
|
||||||
return {
|
return {
|
||||||
componentName: 'Gantt',
|
componentName: `${componentNamePrefix}Gantt`,
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -485,8 +487,9 @@ export const ganttSettings = new SchemaSettings({
|
|||||||
Component: SchemaSettingsTemplate,
|
Component: SchemaSettingsTemplate,
|
||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection();
|
const { name } = useCollection();
|
||||||
|
const { componentNamePrefix } = useBlockTemplateContext();
|
||||||
return {
|
return {
|
||||||
componentName: 'Gantt',
|
componentName: `${componentNamePrefix}Gantt`,
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -14,16 +14,16 @@ import React, { useContext } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useSchemaInitializer,
|
|
||||||
useSchemaInitializerItem,
|
|
||||||
useCollectionManager_deprecated,
|
|
||||||
useGlobalTheme,
|
|
||||||
FormDialog,
|
|
||||||
SchemaComponent,
|
|
||||||
DataBlockInitializer,
|
|
||||||
SchemaComponentOptions,
|
|
||||||
Collection,
|
Collection,
|
||||||
CollectionFieldOptions,
|
CollectionFieldOptions,
|
||||||
|
DataBlockInitializer,
|
||||||
|
FormDialog,
|
||||||
|
SchemaComponent,
|
||||||
|
SchemaComponentOptions,
|
||||||
|
useCollectionManager_deprecated,
|
||||||
|
useGlobalTheme,
|
||||||
|
useSchemaInitializer,
|
||||||
|
useSchemaInitializerItem,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { createGanttBlockUISchema } from './createGanttBlockUISchema';
|
import { createGanttBlockUISchema } from './createGanttBlockUISchema';
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ export const GanttBlockInitializer = ({
|
|||||||
return (
|
return (
|
||||||
<DataBlockInitializer
|
<DataBlockInitializer
|
||||||
{...itemConfig}
|
{...itemConfig}
|
||||||
componentType={'Calendar'}
|
componentType={`Calendar`}
|
||||||
icon={<FormOutlined />}
|
icon={<FormOutlined />}
|
||||||
onCreateBlockSchema={async (options) => {
|
onCreateBlockSchema={async (options) => {
|
||||||
if (createBlockSchema) {
|
if (createBlockSchema) {
|
||||||
|
@ -9,15 +9,17 @@
|
|||||||
|
|
||||||
import { useField, useFieldSchema } from '@formily/react';
|
import { useField, useFieldSchema } from '@formily/react';
|
||||||
import {
|
import {
|
||||||
useFormBlockContext,
|
removeNullCondition,
|
||||||
|
SchemaSettings,
|
||||||
|
SchemaSettingsBlockHeightItem,
|
||||||
|
SchemaSettingsBlockTitleItem,
|
||||||
SchemaSettingsDataScope,
|
SchemaSettingsDataScope,
|
||||||
|
SchemaSettingsTemplate,
|
||||||
|
useBlockTemplateContext,
|
||||||
|
useCollection,
|
||||||
useCollection_deprecated,
|
useCollection_deprecated,
|
||||||
useDesignable,
|
useDesignable,
|
||||||
SchemaSettings,
|
useFormBlockContext,
|
||||||
SchemaSettingsBlockTitleItem,
|
|
||||||
removeNullCondition,
|
|
||||||
SchemaSettingsTemplate,
|
|
||||||
SchemaSettingsBlockHeightItem,
|
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { useKanbanBlockContext } from './KanbanBlockProvider';
|
import { useKanbanBlockContext } from './KanbanBlockProvider';
|
||||||
export const kanbanSettings = new SchemaSettings({
|
export const kanbanSettings = new SchemaSettings({
|
||||||
@ -66,9 +68,10 @@ export const kanbanSettings = new SchemaSettings({
|
|||||||
name: 'template',
|
name: 'template',
|
||||||
Component: SchemaSettingsTemplate,
|
Component: SchemaSettingsTemplate,
|
||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { name } = useCollection();
|
||||||
|
const { componentNamePrefix } = useBlockTemplateContext();
|
||||||
return {
|
return {
|
||||||
componentName: 'Kanban',
|
componentName: `${componentNamePrefix}Kanban`,
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -15,20 +15,20 @@ import { useTranslation } from 'react-i18next';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
APIClientProvider,
|
APIClientProvider,
|
||||||
useCollectionManager_deprecated,
|
Collection,
|
||||||
useGlobalTheme,
|
CollectionFieldOptions,
|
||||||
|
DataBlockInitializer,
|
||||||
FormDialog,
|
FormDialog,
|
||||||
SchemaComponent,
|
SchemaComponent,
|
||||||
SchemaComponentOptions,
|
SchemaComponentOptions,
|
||||||
DataBlockInitializer,
|
useAPIClient,
|
||||||
|
useCollectionManager_deprecated,
|
||||||
|
useGlobalTheme,
|
||||||
useSchemaInitializer,
|
useSchemaInitializer,
|
||||||
useSchemaInitializerItem,
|
useSchemaInitializerItem,
|
||||||
useAPIClient,
|
|
||||||
Collection,
|
|
||||||
CollectionFieldOptions,
|
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { createKanbanBlockUISchema } from './createKanbanBlockUISchema';
|
|
||||||
import { CreateAndSelectSort } from './CreateAndSelectSort';
|
import { CreateAndSelectSort } from './CreateAndSelectSort';
|
||||||
|
import { createKanbanBlockUISchema } from './createKanbanBlockUISchema';
|
||||||
import { NAMESPACE } from './locale';
|
import { NAMESPACE } from './locale';
|
||||||
|
|
||||||
const CreateKanbanForm = ({ item, sortFields, collectionFields, fields, options, api }) => {
|
const CreateKanbanForm = ({ item, sortFields, collectionFields, fields, options, api }) => {
|
||||||
@ -130,7 +130,7 @@ export const KanbanBlockInitializer = ({
|
|||||||
return (
|
return (
|
||||||
<DataBlockInitializer
|
<DataBlockInitializer
|
||||||
{...itemConfig}
|
{...itemConfig}
|
||||||
componentType={'Calendar'}
|
componentType={`Calendar`}
|
||||||
icon={<FormOutlined />}
|
icon={<FormOutlined />}
|
||||||
onCreateBlockSchema={async (options) => {
|
onCreateBlockSchema={async (options) => {
|
||||||
if (createBlockSchema) {
|
if (createBlockSchema) {
|
||||||
|
@ -65,7 +65,7 @@ test('createMapBlockSchema should return an object with expected properties', ()
|
|||||||
},
|
},
|
||||||
"title": "{{ t("View record") }}",
|
"title": "{{ t("View record") }}",
|
||||||
"type": "void",
|
"type": "void",
|
||||||
"x-component": "Action.Drawer",
|
"x-component": "Action.Container",
|
||||||
"x-component-props": {
|
"x-component-props": {
|
||||||
"className": "nb-action-popup",
|
"className": "nb-action-popup",
|
||||||
},
|
},
|
||||||
|
@ -11,6 +11,7 @@ import { ISchema, useField, useFieldSchema } from '@formily/react';
|
|||||||
import {
|
import {
|
||||||
FilterBlockType,
|
FilterBlockType,
|
||||||
SchemaSettings,
|
SchemaSettings,
|
||||||
|
SchemaSettingsBlockHeightItem,
|
||||||
SchemaSettingsBlockTitleItem,
|
SchemaSettingsBlockTitleItem,
|
||||||
SchemaSettingsCascaderItem,
|
SchemaSettingsCascaderItem,
|
||||||
SchemaSettingsConnectDataBlocks,
|
SchemaSettingsConnectDataBlocks,
|
||||||
@ -20,11 +21,11 @@ import {
|
|||||||
SchemaSettingsSelectItem,
|
SchemaSettingsSelectItem,
|
||||||
SchemaSettingsTemplate,
|
SchemaSettingsTemplate,
|
||||||
setDataLoadingModeSettingsItem,
|
setDataLoadingModeSettingsItem,
|
||||||
|
useBlockTemplateContext,
|
||||||
useCollection,
|
useCollection,
|
||||||
useCollectionManager_deprecated,
|
useCollectionManager_deprecated,
|
||||||
useDesignable,
|
useDesignable,
|
||||||
useFormBlockContext,
|
useFormBlockContext,
|
||||||
SchemaSettingsBlockHeightItem,
|
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { useMapTranslation } from '../locale';
|
import { useMapTranslation } from '../locale';
|
||||||
@ -231,10 +232,11 @@ export const mapBlockSettings = new SchemaSettings({
|
|||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection();
|
const { name } = useCollection();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { componentNamePrefix } = useBlockTemplateContext();
|
||||||
const defaultResource =
|
const defaultResource =
|
||||||
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
return {
|
return {
|
||||||
componentName: 'Map',
|
componentName: `${componentNamePrefix}Map`,
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
resourceName: defaultResource,
|
resourceName: defaultResource,
|
||||||
};
|
};
|
||||||
|
@ -22,8 +22,8 @@ import {
|
|||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { useMapTranslation } from '../locale';
|
import { useMapTranslation } from '../locale';
|
||||||
import { findNestedOption } from './utils';
|
|
||||||
import { createMapBlockUISchema } from './createMapBlockUISchema';
|
import { createMapBlockUISchema } from './createMapBlockUISchema';
|
||||||
|
import { findNestedOption } from './utils';
|
||||||
|
|
||||||
export const MapBlockInitializer = () => {
|
export const MapBlockInitializer = () => {
|
||||||
const itemConfig = useSchemaInitializerItem();
|
const itemConfig = useSchemaInitializerItem();
|
||||||
@ -32,9 +32,10 @@ export const MapBlockInitializer = () => {
|
|||||||
const { getCollectionFieldsOptions } = useCollectionManager_deprecated();
|
const { getCollectionFieldsOptions } = useCollectionManager_deprecated();
|
||||||
const { t } = useMapTranslation();
|
const { t } = useMapTranslation();
|
||||||
const { theme } = useGlobalTheme();
|
const { theme } = useGlobalTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataBlockInitializer
|
<DataBlockInitializer
|
||||||
componentType={'Map'}
|
componentType={`Map`}
|
||||||
icon={<TableOutlined />}
|
icon={<TableOutlined />}
|
||||||
onCreateBlockSchema={async ({ item }) => {
|
onCreateBlockSchema={async ({ item }) => {
|
||||||
const mapFieldOptions = getCollectionFieldsOptions(item.name, ['point', 'lineString', 'polygon'], {
|
const mapFieldOptions = getCollectionFieldsOptions(item.name, ['point', 'lineString', 'polygon'], {
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
import { ISchema } from '@formily/react';
|
import { ISchema } from '@formily/react';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import { theme } from 'antd';
|
|
||||||
|
|
||||||
export const createMapBlockUISchema = (options: {
|
export const createMapBlockUISchema = (options: {
|
||||||
collectionName: string;
|
collectionName: string;
|
||||||
@ -49,7 +48,7 @@ export const createMapBlockUISchema = (options: {
|
|||||||
properties: {
|
properties: {
|
||||||
drawer: {
|
drawer: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
'x-component': 'Action.Drawer',
|
'x-component': 'Action.Container',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
className: 'nb-action-popup',
|
className: 'nb-action-popup',
|
||||||
},
|
},
|
||||||
|
@ -326,7 +326,6 @@ export const AMapBlock = (props) => {
|
|||||||
|
|
||||||
const MapBlockDrawer = (props) => {
|
const MapBlockDrawer = (props) => {
|
||||||
const { setVisible, record } = props;
|
const { setVisible, record } = props;
|
||||||
const { t } = useMapTranslation();
|
|
||||||
const collection = useCollection();
|
const collection = useCollection();
|
||||||
const parentRecordData = useCollectionParentRecordData();
|
const parentRecordData = useCollectionParentRecordData();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
@ -375,7 +375,6 @@ export const GoogleMapsBlock = (props) => {
|
|||||||
|
|
||||||
const MapBlockDrawer = (props) => {
|
const MapBlockDrawer = (props) => {
|
||||||
const { setVisible, record } = props;
|
const { setVisible, record } = props;
|
||||||
const { t } = useMapTranslation();
|
|
||||||
const collection = useCollection();
|
const collection = useCollection();
|
||||||
const parentRecordData = useCollectionParentRecordData();
|
const parentRecordData = useCollectionParentRecordData();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* 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 { createStyles } from '@nocobase/client';
|
||||||
|
|
||||||
|
export const useMobileActionDrawerStyle = createStyles(({ css, token }: any) => {
|
||||||
|
return {
|
||||||
|
header: css`
|
||||||
|
height: var(--nb-mobile-page-header-height);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-bottom: 1px solid ${token.colorSplit};
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background-color: white;
|
||||||
|
z-index: 1000;
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
|
||||||
|
placeholder: css`
|
||||||
|
display: inline-block;
|
||||||
|
padding: 12px;
|
||||||
|
visibility: hidden;
|
||||||
|
`,
|
||||||
|
|
||||||
|
closeIcon: css`
|
||||||
|
display: inline-block;
|
||||||
|
padding: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
`,
|
||||||
|
|
||||||
|
body: css`
|
||||||
|
border-top-left-radius: 8px;
|
||||||
|
border-top-right-radius: 8px;
|
||||||
|
max-height: calc(100% - var(--nb-mobile-page-header-height));
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
background-color: ${token.colorBgLayout};
|
||||||
|
`,
|
||||||
|
|
||||||
|
footer: css`
|
||||||
|
padding: 8px var(--nb-mobile-page-tabs-content-padding);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
border-top: 1px solid ${token.colorSplit};
|
||||||
|
background-color: ${token.colorBgLayout};
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
});
|
@ -0,0 +1,127 @@
|
|||||||
|
/**
|
||||||
|
* 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 { observer, RecursionField, useField, useFieldSchema } from '@formily/react';
|
||||||
|
import { Action, SchemaComponent, useActionContext } from '@nocobase/client';
|
||||||
|
import { ConfigProvider } from 'antd';
|
||||||
|
import { Popup } from 'antd-mobile';
|
||||||
|
import { CloseOutline } from 'antd-mobile-icons';
|
||||||
|
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||||
|
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 }) => {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const field = useField();
|
||||||
|
const { visible, setVisible } = useActionContext();
|
||||||
|
const { popupContainerRef, visiblePopup } = usePopupContainer(visible);
|
||||||
|
const { styles } = useMobileActionDrawerStyle();
|
||||||
|
const { basicZIndex } = useBasicZIndex();
|
||||||
|
|
||||||
|
const newZIndex = basicZIndex + MIN_Z_INDEX_INCREMENT;
|
||||||
|
|
||||||
|
const zIndexStyle = useMemo(() => {
|
||||||
|
return {
|
||||||
|
zIndex: newZIndex,
|
||||||
|
};
|
||||||
|
}, [newZIndex]);
|
||||||
|
|
||||||
|
const footerSchema = fieldSchema.reduceProperties((buf, s) => {
|
||||||
|
if (s['x-component'] === props.footerNodeName) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
});
|
||||||
|
|
||||||
|
const title = field.title || '';
|
||||||
|
const marginBlock = 18;
|
||||||
|
|
||||||
|
const closePopup = useCallback(() => {
|
||||||
|
setVisible(false);
|
||||||
|
}, [setVisible]);
|
||||||
|
|
||||||
|
const theme = useMemo(() => {
|
||||||
|
return {
|
||||||
|
token: {
|
||||||
|
marginBlock,
|
||||||
|
borderRadiusBlock: 0,
|
||||||
|
boxShadowTertiary: 'none',
|
||||||
|
zIndexPopupBase: newZIndex,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}, [newZIndex]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BasicZIndexProvider basicZIndex={newZIndex}>
|
||||||
|
<ConfigProvider theme={theme}>
|
||||||
|
<Popup
|
||||||
|
visible={visiblePopup}
|
||||||
|
onClose={closePopup}
|
||||||
|
onMaskClick={closePopup}
|
||||||
|
getContainer={() => popupContainerRef.current}
|
||||||
|
bodyClassName={styles.body}
|
||||||
|
bodyStyle={zIndexStyle}
|
||||||
|
maskStyle={zIndexStyle}
|
||||||
|
closeOnSwipe
|
||||||
|
>
|
||||||
|
<div className={styles.header}>
|
||||||
|
{/* used to make the title center */}
|
||||||
|
<span className={styles.placeholder}>
|
||||||
|
<CloseOutline />
|
||||||
|
</span>
|
||||||
|
<span>{title}</span>
|
||||||
|
<span className={styles.closeIcon} onClick={closePopup}>
|
||||||
|
<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>
|
||||||
|
{footerSchema ? (
|
||||||
|
<div className={styles.footer}>
|
||||||
|
<RecursionField
|
||||||
|
basePath={field.address}
|
||||||
|
schema={fieldSchema}
|
||||||
|
onlyRenderProperties
|
||||||
|
filterProperties={(s) => {
|
||||||
|
return s['x-component'] === props.footerNodeName;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</Popup>
|
||||||
|
</ConfigProvider>
|
||||||
|
</BasicZIndexProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ActionDrawerUsedInMobile.displayName = 'ActionDrawerUsedInMobile';
|
||||||
|
|
||||||
|
const originalActionDrawer = Action.Drawer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* adapt Action.Drawer to mobile
|
||||||
|
*/
|
||||||
|
export const useToAdaptActionDrawerToMobile = () => {
|
||||||
|
Action.Drawer = ActionDrawerUsedInMobile;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
Action.Drawer = originalActionDrawer;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
};
|
@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* 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 React, { useMemo } from 'react';
|
||||||
|
|
||||||
|
const BasicZIndexContext = React.createContext<{
|
||||||
|
basicZIndex: number;
|
||||||
|
}>({
|
||||||
|
basicZIndex: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* used to accumulate z-index in nested popups
|
||||||
|
* @param props
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const BasicZIndexProvider: React.FC<{ basicZIndex: number }> = (props) => {
|
||||||
|
const value = useMemo(() => ({ basicZIndex: props.basicZIndex }), [props.basicZIndex]);
|
||||||
|
return <BasicZIndexContext.Provider value={value}>{props.children}</BasicZIndexContext.Provider>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useBasicZIndex = () => {
|
||||||
|
return React.useContext(BasicZIndexContext);
|
||||||
|
};
|
||||||
|
|
||||||
|
// minimum z-index increment
|
||||||
|
export const MIN_Z_INDEX_INCREMENT = 10;
|
@ -0,0 +1,166 @@
|
|||||||
|
/**
|
||||||
|
* 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 { Filter, withDynamicSchemaProps } from '@nocobase/client';
|
||||||
|
import { ConfigProvider } from 'antd';
|
||||||
|
import { Popup } from 'antd-mobile';
|
||||||
|
import { CloseOutline } from 'antd-mobile-icons';
|
||||||
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useMobileActionDrawerStyle } from './ActionDrawer.style';
|
||||||
|
import { MIN_Z_INDEX_INCREMENT, useBasicZIndex } from './BasicZIndexProvider';
|
||||||
|
|
||||||
|
const OriginFilterAction = Filter.Action;
|
||||||
|
|
||||||
|
export const FilterAction = withDynamicSchemaProps((props) => {
|
||||||
|
return (
|
||||||
|
<OriginFilterAction
|
||||||
|
{...props}
|
||||||
|
Container={(props) => {
|
||||||
|
const { visiblePopup, popupContainerRef } = usePopupContainer(props.open);
|
||||||
|
const { basicZIndex } = useBasicZIndex();
|
||||||
|
const { styles } = useMobileActionDrawerStyle();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const newZIndex = basicZIndex + MIN_Z_INDEX_INCREMENT;
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
|
const closePopup = useCallback(() => {
|
||||||
|
props.onOpenChange(false);
|
||||||
|
}, [props]);
|
||||||
|
|
||||||
|
const theme = useMemo(() => {
|
||||||
|
return {
|
||||||
|
token: {
|
||||||
|
zIndexPopupBase: newZIndex,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}, [newZIndex]);
|
||||||
|
|
||||||
|
const bodyStyle = useMemo(
|
||||||
|
() => ({
|
||||||
|
borderTopLeftRadius: '8px',
|
||||||
|
borderTopRightRadius: '8px',
|
||||||
|
maxHeight: 'calc(100% - var(--nb-mobile-page-header-height))',
|
||||||
|
overflow: 'auto',
|
||||||
|
zIndex: newZIndex,
|
||||||
|
}),
|
||||||
|
[newZIndex],
|
||||||
|
);
|
||||||
|
|
||||||
|
const zIndexStyle = useMemo(() => ({ zIndex: newZIndex }), [newZIndex]);
|
||||||
|
|
||||||
|
const getContainer = useCallback(() => popupContainerRef.current, [popupContainerRef]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfigProvider theme={theme}>
|
||||||
|
{props.children}
|
||||||
|
<Popup
|
||||||
|
visible={visiblePopup}
|
||||||
|
onClose={closePopup}
|
||||||
|
onMaskClick={closePopup}
|
||||||
|
getContainer={getContainer}
|
||||||
|
bodyStyle={bodyStyle}
|
||||||
|
maskStyle={zIndexStyle}
|
||||||
|
closeOnSwipe
|
||||||
|
>
|
||||||
|
<div className={styles.header}>
|
||||||
|
{/* used to make the title center */}
|
||||||
|
<span className={styles.placeholder}>
|
||||||
|
<CloseOutline />
|
||||||
|
</span>
|
||||||
|
<span>{t('Filter')}</span>
|
||||||
|
<span className={styles.closeIcon} onClick={closePopup}>
|
||||||
|
<CloseOutline />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ padding: 12 }}>{props.content}</div>
|
||||||
|
<div style={{ height: 150 }}></div>
|
||||||
|
</Popup>
|
||||||
|
</ConfigProvider>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
FilterAction.displayName = 'FilterAction';
|
||||||
|
|
||||||
|
const originalFilterAction = Filter.Action;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* adapt Filter.Action to mobile
|
||||||
|
*/
|
||||||
|
export const useToAdaptFilterActionToMobile = () => {
|
||||||
|
Filter.Action = FilterAction;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
Filter.Action = originalFilterAction;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 之所以不直接使用 mobile-container 作为容器,是因为会影响到区块的拖拽功能。详见:https://nocobase.height.app/T-4959
|
||||||
|
* @param visible
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const usePopupContainer = (visible: boolean) => {
|
||||||
|
const [mobileContainer] = useState<HTMLElement>(() => document.querySelector('.mobile-container'));
|
||||||
|
const [visiblePopup, setVisiblePopup] = useState(false);
|
||||||
|
const popupContainerRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
const { basicZIndex } = useBasicZIndex();
|
||||||
|
|
||||||
|
const newZIndex = basicZIndex + MIN_Z_INDEX_INCREMENT;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!visible) {
|
||||||
|
setVisiblePopup(false);
|
||||||
|
if (popupContainerRef.current) {
|
||||||
|
// Popup 动画都结束的时候再移除
|
||||||
|
setTimeout(() => {
|
||||||
|
mobileContainer.contains(popupContainerRef.current) && mobileContainer.removeChild(popupContainerRef.current);
|
||||||
|
popupContainerRef.current = null;
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const popupContainer = document.createElement('div');
|
||||||
|
popupContainer.style.transform = 'translateZ(0)';
|
||||||
|
popupContainer.style.position = 'absolute';
|
||||||
|
popupContainer.style.top = '0';
|
||||||
|
popupContainer.style.left = '0';
|
||||||
|
popupContainer.style.right = '0';
|
||||||
|
popupContainer.style.bottom = '0';
|
||||||
|
popupContainer.style.overflow = 'hidden';
|
||||||
|
popupContainer.style.zIndex = newZIndex.toString();
|
||||||
|
|
||||||
|
mobileContainer.appendChild(popupContainer);
|
||||||
|
popupContainerRef.current = popupContainer;
|
||||||
|
|
||||||
|
setVisiblePopup(true);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (popupContainerRef.current) {
|
||||||
|
// Popup 动画都结束的时候再移除
|
||||||
|
setTimeout(() => {
|
||||||
|
mobileContainer.contains(popupContainerRef.current) && mobileContainer.removeChild(popupContainerRef.current);
|
||||||
|
popupContainerRef.current = null;
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [mobileContainer, newZIndex, visible]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
visiblePopup,
|
||||||
|
popupContainerRef,
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,77 @@
|
|||||||
|
/**
|
||||||
|
* 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 { createStyles } from '@nocobase/client';
|
||||||
|
|
||||||
|
export const useInternalPopoverNesterUsedInMobileStyle = createStyles(({ css, token }: any) => {
|
||||||
|
return {
|
||||||
|
header: css`
|
||||||
|
height: var(--nb-mobile-page-header-height);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-bottom: 1px solid ${token.colorSplit};
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background-color: white;
|
||||||
|
z-index: 1000;
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
|
||||||
|
placeholder: css`
|
||||||
|
display: inline-block;
|
||||||
|
padding: 12px;
|
||||||
|
visibility: hidden;
|
||||||
|
`,
|
||||||
|
|
||||||
|
closeIcon: css`
|
||||||
|
display: inline-block;
|
||||||
|
padding: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
`,
|
||||||
|
|
||||||
|
body: css`
|
||||||
|
border-top-left-radius: 8px;
|
||||||
|
border-top-right-radius: 8px;
|
||||||
|
max-height: calc(100% - var(--nb-mobile-page-header-height));
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
// background-color: ${token.colorBgLayout};
|
||||||
|
|
||||||
|
.popover-subform-container {
|
||||||
|
min-width: initial;
|
||||||
|
max-width: initial;
|
||||||
|
max-height: initial;
|
||||||
|
overflow: hidden;
|
||||||
|
.ant-card {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
|
||||||
|
footer: css`
|
||||||
|
padding: 8px var(--nb-mobile-page-tabs-content-padding);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
border-top: 1px solid ${token.colorSplit};
|
||||||
|
background-color: ${token.colorBgLayout};
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
});
|
@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* 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 { useField } from '@formily/react';
|
||||||
|
import { ConfigProvider } from 'antd';
|
||||||
|
import { Popup } from 'antd-mobile';
|
||||||
|
import { CloseOutline } from 'antd-mobile-icons';
|
||||||
|
import React, { useCallback, useMemo } from 'react';
|
||||||
|
import { BasicZIndexProvider, MIN_Z_INDEX_INCREMENT, useBasicZIndex } from './BasicZIndexProvider';
|
||||||
|
import { usePopupContainer } from './FilterAction';
|
||||||
|
import { useInternalPopoverNesterUsedInMobileStyle } from './InternalPopoverNester.style';
|
||||||
|
|
||||||
|
const Container = (props) => {
|
||||||
|
const { onOpenChange } = props;
|
||||||
|
const { visiblePopup, popupContainerRef } = usePopupContainer(props.open);
|
||||||
|
const { styles } = useInternalPopoverNesterUsedInMobileStyle();
|
||||||
|
const field = useField();
|
||||||
|
const { basicZIndex } = useBasicZIndex();
|
||||||
|
|
||||||
|
const newZIndex = basicZIndex + MIN_Z_INDEX_INCREMENT;
|
||||||
|
const title = field.title || '';
|
||||||
|
|
||||||
|
const zIndexStyle = useMemo(() => {
|
||||||
|
return {
|
||||||
|
zIndex: newZIndex,
|
||||||
|
};
|
||||||
|
}, [newZIndex]);
|
||||||
|
|
||||||
|
const closePopup = useCallback(() => {
|
||||||
|
onOpenChange(false);
|
||||||
|
}, [onOpenChange]);
|
||||||
|
|
||||||
|
const openPopup = useCallback(() => {
|
||||||
|
onOpenChange(true);
|
||||||
|
}, [onOpenChange]);
|
||||||
|
|
||||||
|
const theme = useMemo(() => {
|
||||||
|
return {
|
||||||
|
token: {
|
||||||
|
zIndexPopupBase: newZIndex,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}, [newZIndex]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BasicZIndexProvider basicZIndex={newZIndex}>
|
||||||
|
<ConfigProvider theme={theme}>
|
||||||
|
<div onClick={openPopup}>{props.children}</div>
|
||||||
|
<Popup
|
||||||
|
visible={visiblePopup}
|
||||||
|
onClose={closePopup}
|
||||||
|
onMaskClick={closePopup}
|
||||||
|
getContainer={() => popupContainerRef.current as HTMLElement}
|
||||||
|
bodyClassName={styles.body}
|
||||||
|
bodyStyle={zIndexStyle}
|
||||||
|
maskStyle={zIndexStyle}
|
||||||
|
showCloseButton
|
||||||
|
closeOnSwipe
|
||||||
|
>
|
||||||
|
<div className={styles.header}>
|
||||||
|
{/* used to make the title center */}
|
||||||
|
<span className={styles.placeholder}>
|
||||||
|
<CloseOutline />
|
||||||
|
</span>
|
||||||
|
<span>{title}</span>
|
||||||
|
<span className={styles.closeIcon} onClick={closePopup}>
|
||||||
|
<CloseOutline />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{props.content}
|
||||||
|
<div style={{ height: 50 }}></div>
|
||||||
|
</Popup>
|
||||||
|
</ConfigProvider>
|
||||||
|
</BasicZIndexProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InternalPopoverNesterUsedInMobile: React.FC<{ OriginComponent: React.FC<any> }> = (props) => {
|
||||||
|
const { OriginComponent } = props;
|
||||||
|
|
||||||
|
return <OriginComponent {...props} Container={Container} />;
|
||||||
|
};
|
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* 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 { SchemaComponentOptions, useSchemaOptionsContext } from '@nocobase/client';
|
||||||
|
import React, { FC, useMemo } from 'react';
|
||||||
|
import { useGridCardBlockDecoratorProps } from './useGridCardBlockDecoratorProps';
|
||||||
|
|
||||||
|
/* 使用移动端专属的 scope 覆盖桌面端的 scope,用于在移动端适配桌面端区块 */
|
||||||
|
export const ResetSchemaOptionsProvider: FC = (props) => {
|
||||||
|
const { scope: desktopScopes } = useSchemaOptionsContext();
|
||||||
|
const scopes = useMemo(
|
||||||
|
() => ({
|
||||||
|
useGridCardBlockDecoratorProps: (props) =>
|
||||||
|
useGridCardBlockDecoratorProps(props, desktopScopes?.useGridCardBlockDecoratorProps),
|
||||||
|
}),
|
||||||
|
[desktopScopes?.useGridCardBlockDecoratorProps],
|
||||||
|
);
|
||||||
|
return <SchemaComponentOptions scope={scopes}>{props.children}</SchemaComponentOptions>;
|
||||||
|
};
|
@ -23,5 +23,19 @@ export const useMobileActionPageStyle = createStyles(({ css, token }: any) => {
|
|||||||
margin: 20px;
|
margin: 20px;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
|
||||||
|
footer: css`
|
||||||
|
height: var(--nb-mobile-page-header-height);
|
||||||
|
padding-right: var(--nb-mobile-page-tabs-content-padding);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: white;
|
||||||
|
z-index: 1000;
|
||||||
|
`,
|
||||||
};
|
};
|
||||||
});
|
});
|
@ -0,0 +1,147 @@
|
|||||||
|
/**
|
||||||
|
* 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 { RecursionField, useField, useFieldSchema } from '@formily/react';
|
||||||
|
import {
|
||||||
|
BackButtonUsedInSubPage,
|
||||||
|
SchemaComponent,
|
||||||
|
SchemaInitializer,
|
||||||
|
TabsContextProvider,
|
||||||
|
useActionContext,
|
||||||
|
useApp,
|
||||||
|
useTabsContext,
|
||||||
|
} from '@nocobase/client';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
import { usePluginTranslation } from '../../locale';
|
||||||
|
import { BasicZIndexProvider, MIN_Z_INDEX_INCREMENT, useBasicZIndex } from '../BasicZIndexProvider';
|
||||||
|
import { useMobileActionPageStyle } from './MobileActionPage.style';
|
||||||
|
import { MobileTabsForMobileActionPage } from './MobileTabsForMobileActionPage';
|
||||||
|
|
||||||
|
const components = { Tabs: MobileTabsForMobileActionPage };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把 popup:common:addBlock 替换为移动端专属的值。当退出子页面时,再换回来。
|
||||||
|
*
|
||||||
|
* 之所以要把这个过程放到子页面组件这里,是因为 dataBlocks 的 useChildren 必须要在子页面的上下文中运行。
|
||||||
|
*
|
||||||
|
* @param supportsDataBlocks 支持在子页面中使用的数据区块 name
|
||||||
|
*/
|
||||||
|
const useMobileBlockInitializersInSubpage = (
|
||||||
|
supportsDataBlocks = ['details', 'editForm', 'createForm', 'table', 'gridCard'],
|
||||||
|
) => {
|
||||||
|
const app = useApp();
|
||||||
|
const [originalInitializers] = useState<SchemaInitializer>(() =>
|
||||||
|
app.schemaInitializerManager.get('popup:common:addBlock'),
|
||||||
|
);
|
||||||
|
const { t } = usePluginTranslation();
|
||||||
|
const { visible } = useActionContext();
|
||||||
|
|
||||||
|
const dataBlocks = originalInitializers.options.items.find((item) => item.name === 'dataBlocks');
|
||||||
|
const dataBlocksChildren = [...dataBlocks.useChildren(), ...dataBlocks.children];
|
||||||
|
|
||||||
|
const [newInitializers] = useState<SchemaInitializer>(() => {
|
||||||
|
const options = _.cloneDeep(originalInitializers.options);
|
||||||
|
options.items = options.items.filter((item) => {
|
||||||
|
if (item.name === 'dataBlocks') {
|
||||||
|
item.title = t('Desktop data blocks');
|
||||||
|
item.children = dataBlocksChildren.filter((child) => {
|
||||||
|
return supportsDataBlocks.includes(child.name);
|
||||||
|
});
|
||||||
|
item.useChildren = () => [];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.name === 'otherBlocks') {
|
||||||
|
item.title = t('Other desktop blocks');
|
||||||
|
}
|
||||||
|
|
||||||
|
return item.name !== 'filterBlocks';
|
||||||
|
});
|
||||||
|
|
||||||
|
return new SchemaInitializer(options);
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
app.schemaInitializerManager.add(originalInitializers);
|
||||||
|
};
|
||||||
|
}, [app, originalInitializers]);
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
// 把 PC 端子页面的 Add block 按钮换成移动端的。在退出移动端时,再换回来
|
||||||
|
app.schemaInitializerManager.add(newInitializers);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在移动端通过 Action 按钮打开的页面
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const MobileActionPage = ({ level, footerNodeName }) => {
|
||||||
|
useMobileBlockInitializersInSubpage();
|
||||||
|
|
||||||
|
const field = useField();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const ctx = useActionContext();
|
||||||
|
const { styles } = useMobileActionPageStyle();
|
||||||
|
const tabContext = useTabsContext();
|
||||||
|
const containerDOM = useMemo(() => document.querySelector('.nb-mobile-subpages-slot'), []);
|
||||||
|
const { basicZIndex } = useBasicZIndex();
|
||||||
|
|
||||||
|
// in nested popups, basicZIndex is an accumulated value to ensure that
|
||||||
|
// the z-index of the current level is always higher than the previous level
|
||||||
|
const newZIndex = basicZIndex + MIN_Z_INDEX_INCREMENT + (level || 1);
|
||||||
|
|
||||||
|
const footerSchema = fieldSchema.reduceProperties((buf, s) => {
|
||||||
|
if (s['x-component'] === footerNodeName) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
});
|
||||||
|
|
||||||
|
const zIndexStyle = useMemo(() => {
|
||||||
|
return {
|
||||||
|
zIndex: newZIndex,
|
||||||
|
};
|
||||||
|
}, [newZIndex]);
|
||||||
|
|
||||||
|
if (!ctx.visible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionPageNode = (
|
||||||
|
<BasicZIndexProvider basicZIndex={newZIndex}>
|
||||||
|
<div className={styles.container} style={zIndexStyle}>
|
||||||
|
<TabsContextProvider {...tabContext} tabBarExtraContent={<BackButtonUsedInSubPage />} tabBarGutter={48}>
|
||||||
|
<SchemaComponent components={components} schema={fieldSchema} onlyRenderProperties />
|
||||||
|
</TabsContextProvider>
|
||||||
|
{footerSchema && (
|
||||||
|
<div className={styles.footer} style={zIndexStyle}>
|
||||||
|
<RecursionField
|
||||||
|
basePath={field.address}
|
||||||
|
schema={fieldSchema}
|
||||||
|
onlyRenderProperties
|
||||||
|
filterProperties={(s) => {
|
||||||
|
return s['x-component'] === footerNodeName;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</BasicZIndexProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (containerDOM) {
|
||||||
|
return createPortal(actionPageNode, containerDOM);
|
||||||
|
}
|
||||||
|
|
||||||
|
return actionPageNode;
|
||||||
|
};
|
@ -7,7 +7,7 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { observer, RecursionField, useField, useFieldSchema } from '@formily/react';
|
import { observer, RecursionField, Schema, useField, useFieldSchema } from '@formily/react';
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
DndContext,
|
DndContext,
|
||||||
@ -23,22 +23,35 @@ import {
|
|||||||
import { Tabs } from 'antd-mobile';
|
import { Tabs } from 'antd-mobile';
|
||||||
import { LeftOutline } from 'antd-mobile-icons';
|
import { LeftOutline } from 'antd-mobile-icons';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { useMemo, useRef } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { MobilePageHeader } from '../dynamic-page';
|
import { MobilePageHeader } from '../../pages/dynamic-page';
|
||||||
import { MobilePageContentContainer } from '../dynamic-page/content/MobilePageContentContainer';
|
import { MobilePageContentContainer } from '../../pages/dynamic-page/content/MobilePageContentContainer';
|
||||||
import { useStyles } from '../dynamic-page/header/tabs';
|
import { useStyles } from '../../pages/dynamic-page/header/tabs';
|
||||||
import { useMobileTabsForMobileActionPageStyle } from './MobileTabsForMobileActionPage.style';
|
import { useMobileTabsForMobileActionPageStyle } from './MobileTabsForMobileActionPage.style';
|
||||||
|
|
||||||
export const MobileTabsForMobileActionPage: any = observer(
|
export const MobileTabsForMobileActionPage: any = observer(
|
||||||
(props) => {
|
(props) => {
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { render } = useSchemaInitializerRender(fieldSchema['x-initializer'], fieldSchema['x-initializer-props']);
|
const { render } = useSchemaInitializerRender(fieldSchema['x-initializer'], fieldSchema['x-initializer-props']);
|
||||||
const { activeKey, onChange } = useTabsContext() || {};
|
const { activeKey: _activeKey, onChange: _onChange } = useTabsContext() || {};
|
||||||
|
const [activeKey, setActiveKey] = useState(_activeKey);
|
||||||
const { styles } = useStyles();
|
const { styles } = useStyles();
|
||||||
const { styles: mobileTabsForMobileActionPageStyle } = useMobileTabsForMobileActionPageStyle();
|
const { styles: mobileTabsForMobileActionPageStyle } = useMobileTabsForMobileActionPageStyle();
|
||||||
const { goBack } = useBackButton();
|
const { goBack } = useBackButton();
|
||||||
const keyToTabRef = useRef({});
|
const keyToTabRef = useRef({});
|
||||||
|
|
||||||
|
const onChange = useCallback(
|
||||||
|
(key) => {
|
||||||
|
setActiveKey(key);
|
||||||
|
_onChange?.(key);
|
||||||
|
},
|
||||||
|
[_onChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setActiveKey(_activeKey);
|
||||||
|
}, [_activeKey]);
|
||||||
|
|
||||||
const items = useMemo(() => {
|
const items = useMemo(() => {
|
||||||
const result = fieldSchema.mapProperties((schema, key) => {
|
const result = fieldSchema.mapProperties((schema, key) => {
|
||||||
keyToTabRef.current[key] = <SchemaComponent name={key} schema={schema} onlyRenderProperties distributed />;
|
keyToTabRef.current[key] = <SchemaComponent name={key} schema={schema} onlyRenderProperties distributed />;
|
||||||
@ -50,6 +63,7 @@ export const MobileTabsForMobileActionPage: any = observer(
|
|||||||
|
|
||||||
const tabContent = useMemo(() => {
|
const tabContent = useMemo(() => {
|
||||||
const list = fieldSchema.mapProperties((schema, key) => {
|
const list = fieldSchema.mapProperties((schema, key) => {
|
||||||
|
schema = hideDivider(schema);
|
||||||
return {
|
return {
|
||||||
key,
|
key,
|
||||||
node: <SchemaComponent name={key} schema={schema} onlyRenderProperties distributed />,
|
node: <SchemaComponent name={key} schema={schema} onlyRenderProperties distributed />,
|
||||||
@ -148,3 +162,17 @@ MobileTabsForMobileActionPage.TabPane = observer(
|
|||||||
);
|
);
|
||||||
|
|
||||||
MobileTabsForMobileActionPage.Designer = TabsOfPC.Designer;
|
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;
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于设置 Grid Card 区块的 props,使其样式适配移动端;
|
||||||
|
* @param oldUseGridCardBlockDecoratorProps
|
||||||
|
*/
|
||||||
|
export const useGridCardBlockDecoratorProps = (props, useGridCardBlockDecoratorPropsOfDesktop: any) => {
|
||||||
|
const oldProps = useGridCardBlockDecoratorPropsOfDesktop(props);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...oldProps,
|
||||||
|
// 在移动端中,无论是什么屏幕尺寸,都只显示一列
|
||||||
|
columnCount: {
|
||||||
|
lg: 1,
|
||||||
|
md: 1,
|
||||||
|
xs: 1,
|
||||||
|
xxl: 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -9,11 +9,11 @@
|
|||||||
|
|
||||||
import { PagePopups, Plugin, RouterManager, createRouterManager } from '@nocobase/client';
|
import { PagePopups, Plugin, RouterManager, createRouterManager } from '@nocobase/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Outlet } from 'react-router-dom';
|
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { name } from '../../package.json';
|
import { name } from '../../package.json';
|
||||||
|
|
||||||
|
import { Outlet } from 'react-router-dom';
|
||||||
|
|
||||||
import { generatePluginTranslationTemplate } from './locale';
|
import { generatePluginTranslationTemplate } from './locale';
|
||||||
import { Mobile } from './mobile';
|
import { Mobile } from './mobile';
|
||||||
import {
|
import {
|
||||||
@ -184,6 +184,15 @@ export class PluginMobileClient extends Plugin {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 跳转到主应用的页面
|
||||||
|
this.mobileRouter.add('admin', {
|
||||||
|
path: `/admin/*`,
|
||||||
|
Component: () => {
|
||||||
|
window.location.replace(window.location.href.replace(this.mobilePath, ''));
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
this.mobileRouter.add('mobile.schema', {
|
this.mobileRouter.add('mobile.schema', {
|
||||||
element: <Outlet />,
|
element: <Outlet />,
|
||||||
});
|
});
|
||||||
|
@ -22,6 +22,7 @@ export const MobileProviders: FC<MobileProvidersProps> = ({ children, skipLogin
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.body.style.setProperty('--nb-mobile-page-tabs-content-padding', '12px');
|
document.body.style.setProperty('--nb-mobile-page-tabs-content-padding', '12px');
|
||||||
|
document.body.style.setProperty('--nb-mobile-page-header-height', '50px');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -7,10 +7,10 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Spin } from 'antd';
|
|
||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
import React, { createContext, useContext, useEffect, useMemo } from 'react';
|
|
||||||
import { APIClient, useAPIClient, useRequest } from '@nocobase/client';
|
import { APIClient, useAPIClient, useRequest } from '@nocobase/client';
|
||||||
|
import { Spin } from 'antd';
|
||||||
|
import React, { createContext, useContext, useEffect, useMemo } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import type { IResource } from '@nocobase/sdk';
|
import type { IResource } from '@nocobase/sdk';
|
||||||
|
|
||||||
@ -27,6 +27,7 @@ export interface MobileRouteItem {
|
|||||||
children?: MobileRouteItem[];
|
children?: MobileRouteItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const MobileRoutesContext = createContext<MobileRoutesContextValue>(null);
|
||||||
|
|
||||||
export interface MobileRoutesContextValue {
|
export interface MobileRoutesContextValue {
|
||||||
routeList?: MobileRouteItem[];
|
routeList?: MobileRouteItem[];
|
||||||
@ -37,8 +38,6 @@ export interface MobileRoutesContextValue {
|
|||||||
activeTabItem?: MobileRouteItem;
|
activeTabItem?: MobileRouteItem;
|
||||||
api: APIClient;
|
api: APIClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MobileRoutesContext = createContext<MobileRoutesContextValue>(null);
|
|
||||||
MobileRoutesContext.displayName = 'MobileRoutesContext';
|
MobileRoutesContext.displayName = 'MobileRoutesContext';
|
||||||
|
|
||||||
export const useMobileRoutes = () => {
|
export const useMobileRoutes = () => {
|
||||||
@ -97,7 +96,9 @@ export const MobileRoutesProvider = ({ children }) => {
|
|||||||
data,
|
data,
|
||||||
runAsync: refresh,
|
runAsync: refresh,
|
||||||
loading,
|
loading,
|
||||||
} = useRequest<{ data: MobileRouteItem[] }>(() => resource.list({ tree: true, sort: 'sort' }).then((res) => res.data));
|
} = useRequest<{ data: MobileRouteItem[] }>(() =>
|
||||||
|
resource.list({ tree: true, sort: 'sort' }).then((res) => res.data),
|
||||||
|
);
|
||||||
const routeList = useMemo(() => data?.data || [], [data]);
|
const routeList = useMemo(() => data?.data || [], [data]);
|
||||||
const { activeTabBarItem, activeTabItem } = useActiveTabBar(routeList);
|
const { activeTabBarItem, activeTabItem } = useActiveTabBar(routeList);
|
||||||
|
|
||||||
|
@ -7,21 +7,38 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Action, AntdAppProvider, GlobalThemeProvider, OpenModeProvider, usePlugin } from '@nocobase/client';
|
import {
|
||||||
|
Action,
|
||||||
|
AntdAppProvider,
|
||||||
|
AssociationFieldModeProvider,
|
||||||
|
BlockTemplateProvider,
|
||||||
|
GlobalThemeProvider,
|
||||||
|
OpenModeProvider,
|
||||||
|
usePlugin,
|
||||||
|
} from '@nocobase/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { isDesktop } from 'react-device-detect';
|
import { isDesktop } from 'react-device-detect';
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { ActionDrawerUsedInMobile, useToAdaptActionDrawerToMobile } from '../adaptor-of-desktop/ActionDrawer';
|
||||||
|
import { BasicZIndexProvider } from '../adaptor-of-desktop/BasicZIndexProvider';
|
||||||
|
import { useToAdaptFilterActionToMobile } from '../adaptor-of-desktop/FilterAction';
|
||||||
|
import { InternalPopoverNesterUsedInMobile } from '../adaptor-of-desktop/InternalPopoverNester';
|
||||||
|
import { MobileActionPage } from '../adaptor-of-desktop/mobile-action-page/MobileActionPage';
|
||||||
|
import { ResetSchemaOptionsProvider } from '../adaptor-of-desktop/ResetSchemaOptionsProvider';
|
||||||
import { PageBackgroundColor } from '../constants';
|
import { PageBackgroundColor } from '../constants';
|
||||||
import { DesktopMode } from '../desktop-mode/DesktopMode';
|
import { DesktopMode } from '../desktop-mode/DesktopMode';
|
||||||
import { PluginMobileClient } from '../index';
|
import { PluginMobileClient } from '../index';
|
||||||
import { MobileActionPage } from '../pages/mobile-action-page/MobileActionPage';
|
|
||||||
import { MobileAppProvider } from './MobileAppContext';
|
import { MobileAppProvider } from './MobileAppContext';
|
||||||
import { useStyles } from './styles';
|
import { useStyles } from './styles';
|
||||||
|
|
||||||
export const Mobile = () => {
|
export const Mobile = () => {
|
||||||
|
useToAdaptFilterActionToMobile();
|
||||||
|
useToAdaptActionDrawerToMobile();
|
||||||
|
|
||||||
|
const { styles } = useStyles();
|
||||||
const mobilePlugin = usePlugin(PluginMobileClient);
|
const mobilePlugin = usePlugin(PluginMobileClient);
|
||||||
const MobileRouter = mobilePlugin.getRouterComponent();
|
const MobileRouter = mobilePlugin.getRouterComponent();
|
||||||
const { styles } = useStyles();
|
|
||||||
// 设置的移动端 meta
|
// 设置的移动端 meta
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!isDesktop) {
|
if (!isDesktop) {
|
||||||
@ -44,10 +61,17 @@ export const Mobile = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const DesktopComponent = mobilePlugin.desktopMode === false ? React.Fragment : DesktopMode;
|
const DesktopComponent = mobilePlugin.desktopMode === false ? React.Fragment : DesktopMode;
|
||||||
|
const modeToComponent = React.useMemo(() => {
|
||||||
|
return {
|
||||||
|
PopoverNester: _.memoize((OriginComponent) => (props) => (
|
||||||
|
<InternalPopoverNesterUsedInMobile {...props} OriginComponent={OriginComponent} />
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DesktopComponent>
|
<DesktopComponent>
|
||||||
{/* 目前移动端由于和客户端的主题对不上,所以先使用 `GlobalThemeProvider` 和 `AntdAppProvider` 进行重置为默认主题 */}
|
{/* 目前移动端由于和客户端的主题对不上,所以先使用 `GlobalThemeProvider` 和 `AntdAppProvider` 进行重置为默认主题 */}
|
||||||
<div className={styles.nbMobile}>
|
|
||||||
<GlobalThemeProvider
|
<GlobalThemeProvider
|
||||||
theme={{
|
theme={{
|
||||||
token: {
|
token: {
|
||||||
@ -57,23 +81,31 @@ export const Mobile = () => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AntdAppProvider>
|
<AntdAppProvider className={`mobile-container ${styles.nbMobile}`}>
|
||||||
<OpenModeProvider
|
<OpenModeProvider
|
||||||
defaultOpenMode="page"
|
defaultOpenMode="page"
|
||||||
hideOpenMode
|
hideOpenMode
|
||||||
openModeToComponent={{
|
openModeToComponent={{
|
||||||
page: MobileActionPage,
|
page: MobileActionPage,
|
||||||
drawer: MobileActionPage,
|
drawer: ActionDrawerUsedInMobile,
|
||||||
modal: Action.Modal,
|
modal: Action.Modal,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<BlockTemplateProvider componentNamePrefix="mobile-">
|
||||||
<MobileAppProvider>
|
<MobileAppProvider>
|
||||||
|
<ResetSchemaOptionsProvider>
|
||||||
|
<AssociationFieldModeProvider modeToComponent={modeToComponent}>
|
||||||
|
{/* the z-index of all popups and subpages will be based on this value */}
|
||||||
|
<BasicZIndexProvider basicZIndex={1000}>
|
||||||
<MobileRouter />
|
<MobileRouter />
|
||||||
|
</BasicZIndexProvider>
|
||||||
|
</AssociationFieldModeProvider>
|
||||||
|
</ResetSchemaOptionsProvider>
|
||||||
</MobileAppProvider>
|
</MobileAppProvider>
|
||||||
|
</BlockTemplateProvider>
|
||||||
</OpenModeProvider>
|
</OpenModeProvider>
|
||||||
</AntdAppProvider>
|
</AntdAppProvider>
|
||||||
</GlobalThemeProvider>
|
</GlobalThemeProvider>
|
||||||
</div>
|
|
||||||
</DesktopComponent>
|
</DesktopComponent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -19,6 +19,19 @@ export const useStyles = createStyles(({ token, css }) => {
|
|||||||
.ant-table-thead button[aria-label*='schema-initializer-TableV2-table:configureColumns'] > .ant-btn-icon {
|
.ant-table-thead button[aria-label*='schema-initializer-TableV2-table:configureColumns'] > .ant-btn-icon {
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reset Select record popup
|
||||||
|
.ant-table-thead
|
||||||
|
button[aria-label*='schema-initializer-TableV2.Selector-table:configureColumns']
|
||||||
|
> span:last-child {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.ant-table-thead
|
||||||
|
button[aria-label*='schema-initializer-TableV2.Selector-table:configureColumns']
|
||||||
|
> .ant-btn-icon {
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-pagination .ant-pagination-total-text {
|
.ant-pagination .ant-pagination-total-text {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ export const mobileAddBlockInitializer = new SchemaInitializer({
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: 'dataBlocks',
|
name: 'dataBlocks',
|
||||||
title: '{{t("Data blocks")}}',
|
title: '{{t("Desktop data blocks")}}',
|
||||||
type: 'itemGroup',
|
type: 'itemGroup',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@ -53,7 +53,7 @@ export const mobileAddBlockInitializer = new SchemaInitializer({
|
|||||||
{
|
{
|
||||||
name: 'otherBlocks',
|
name: 'otherBlocks',
|
||||||
type: 'itemGroup',
|
type: 'itemGroup',
|
||||||
title: '{{t("Other blocks")}}',
|
title: '{{t("Other desktop blocks")}}',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
name: 'markdown',
|
name: 'markdown',
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 { useFieldSchema } from '@formily/react';
|
|
||||||
import {
|
|
||||||
BackButtonUsedInSubPage,
|
|
||||||
SchemaComponent,
|
|
||||||
TabsContextProvider,
|
|
||||||
useActionContext,
|
|
||||||
useTabsContext,
|
|
||||||
} from '@nocobase/client';
|
|
||||||
import React, { useMemo } from 'react';
|
|
||||||
import { createPortal } from 'react-dom';
|
|
||||||
import { useMobileActionPageStyle } from './MobileActionPage.style';
|
|
||||||
import { MobileTabsForMobileActionPage } from './MobileTabsForMobileActionPage';
|
|
||||||
|
|
||||||
const components = { Tabs: MobileTabsForMobileActionPage };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 在移动端通过 Action 按钮打开的页面
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const MobileActionPage = ({ level }) => {
|
|
||||||
const filedSchema = useFieldSchema();
|
|
||||||
const ctx = useActionContext();
|
|
||||||
const { styles } = useMobileActionPageStyle();
|
|
||||||
const tabContext = useTabsContext();
|
|
||||||
const containerDOM = useMemo(() => document.querySelector('.nb-mobile-subpages-slot'), []);
|
|
||||||
|
|
||||||
const style = useMemo(() => {
|
|
||||||
return {
|
|
||||||
// 10 为基数,是为了要确保能大于 Table 中的悬浮行的 z-index
|
|
||||||
zIndex: 10 + level,
|
|
||||||
};
|
|
||||||
}, [level]);
|
|
||||||
|
|
||||||
if (!ctx.visible) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const actionPageNode = (
|
|
||||||
<div className={styles.container} style={style}>
|
|
||||||
<TabsContextProvider {...tabContext} tabBarExtraContent={<BackButtonUsedInSubPage />} tabBarGutter={48}>
|
|
||||||
<SchemaComponent components={components} schema={filedSchema} onlyRenderProperties />
|
|
||||||
</TabsContextProvider>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (containerDOM) {
|
|
||||||
return createPortal(actionPageNode, containerDOM);
|
|
||||||
}
|
|
||||||
|
|
||||||
return actionPageNode;
|
|
||||||
};
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user