Merge pull request '重构:调整 schema-components 和 components 中的组件,现在能相对清晰反馈他们的用途' (#400) from feat_drawer_in_page into @hera/dev
Reviewed-on: daoyoucloud/tachycode#400 Reviewed-by: wangjiahui <wwwjh0710@163.com>
This commit is contained in:
		
						commit
						7f2755a336
					
				@ -11,8 +11,8 @@ export const ErrorFallback: FC<FallbackProps> = ({ error }) => {
 | 
			
		||||
 | 
			
		||||
  const subTitle = (
 | 
			
		||||
    <Trans>
 | 
			
		||||
      {'This is likely a NocoBase internals bug. Please open an issue at '}
 | 
			
		||||
      <Link href="https://github.com/nocobase/nocobase/issues" target="_blank">
 | 
			
		||||
      {'This is likely a TachyCode internals bug. Please open an issue at '}
 | 
			
		||||
      <Link href="https://git.daoyoucloud.com/daoyoucloud/tachycode/issues" target="_blank">
 | 
			
		||||
        here
 | 
			
		||||
      </Link>
 | 
			
		||||
    </Trans>
 | 
			
		||||
@ -26,7 +26,12 @@ export const ErrorFallback: FC<FallbackProps> = ({ error }) => {
 | 
			
		||||
        title={t('Render Failed')}
 | 
			
		||||
        subTitle={subTitle}
 | 
			
		||||
        extra={[
 | 
			
		||||
          <Button type="primary" key="feedback" href="https://github.com/nocobase/nocobase/issues" target="_blank">
 | 
			
		||||
          <Button
 | 
			
		||||
            type="primary"
 | 
			
		||||
            key="feedback"
 | 
			
		||||
            href="https://git.daoyoucloud.com/daoyoucloud/tachycode/issues"
 | 
			
		||||
            target="_blank"
 | 
			
		||||
          >
 | 
			
		||||
            {t('Feedback')}
 | 
			
		||||
          </Button>,
 | 
			
		||||
          <Button key="try" onClick={resetBoundary}>
 | 
			
		||||
@ -1 +1 @@
 | 
			
		||||
export * from './ErrorFallback';
 | 
			
		||||
export * from './ErrorFallback2';
 | 
			
		||||
 | 
			
		||||
@ -1,86 +0,0 @@
 | 
			
		||||
import { ISchema, useFieldSchema } from '@formily/react';
 | 
			
		||||
import { SchemaSettingsModalItem, useDesignable } from '@nocobase/client';
 | 
			
		||||
import { useTranslation } from '../locale';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
 | 
			
		||||
export function AfterSuccess() {
 | 
			
		||||
  const { dn } = useDesignable();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const component = fieldSchema.parent.parent['x-component'];
 | 
			
		||||
  const schema = {
 | 
			
		||||
    type: 'object',
 | 
			
		||||
    title: t('After successful submission'),
 | 
			
		||||
    properties: {
 | 
			
		||||
      successMessage: {
 | 
			
		||||
        title: t('Popup message'),
 | 
			
		||||
        'x-decorator': 'FormItem',
 | 
			
		||||
        'x-component': 'Input.TextArea',
 | 
			
		||||
        'x-component-props': {},
 | 
			
		||||
      },
 | 
			
		||||
      dataClear: {
 | 
			
		||||
        title: t('Clear data method'),
 | 
			
		||||
        enum: [
 | 
			
		||||
          { label: t('Automatic clear'), value: false },
 | 
			
		||||
          { label: t('Manually clear'), value: true },
 | 
			
		||||
        ],
 | 
			
		||||
        'x-decorator': 'FormItem',
 | 
			
		||||
        'x-component': 'Radio.Group',
 | 
			
		||||
        'x-component-props': {},
 | 
			
		||||
      },
 | 
			
		||||
      manualClose: {
 | 
			
		||||
        title: t('Popup close method'),
 | 
			
		||||
        enum: [
 | 
			
		||||
          { label: t('Automatic close'), value: false },
 | 
			
		||||
          { label: t('Manually close'), value: true },
 | 
			
		||||
        ],
 | 
			
		||||
        'x-decorator': 'FormItem',
 | 
			
		||||
        'x-component': 'Radio.Group',
 | 
			
		||||
        'x-component-props': {},
 | 
			
		||||
      },
 | 
			
		||||
      redirecting: {
 | 
			
		||||
        title: t('Then'),
 | 
			
		||||
        enum: [
 | 
			
		||||
          { label: t('Stay on current page'), value: false },
 | 
			
		||||
          { label: t('Redirect to'), value: true },
 | 
			
		||||
        ],
 | 
			
		||||
        'x-decorator': 'FormItem',
 | 
			
		||||
        'x-component': 'Radio.Group',
 | 
			
		||||
        'x-component-props': {},
 | 
			
		||||
        'x-reactions': {
 | 
			
		||||
          target: 'redirectTo',
 | 
			
		||||
          fulfill: {
 | 
			
		||||
            state: {
 | 
			
		||||
              visible: '{{!!$self.value}}',
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      redirectTo: {
 | 
			
		||||
        title: t('Link'),
 | 
			
		||||
        'x-decorator': 'FormItem',
 | 
			
		||||
        'x-component': 'Input',
 | 
			
		||||
        'x-component-props': {},
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
  if (!(component as string).includes('Form')) {
 | 
			
		||||
    delete schema.properties.dataClear;
 | 
			
		||||
  }
 | 
			
		||||
  return (
 | 
			
		||||
    <SchemaSettingsModalItem
 | 
			
		||||
      title={t('After successful submission')}
 | 
			
		||||
      initialValues={fieldSchema?.['x-action-settings']?.['onSuccess']}
 | 
			
		||||
      schema={{ ...schema } as ISchema}
 | 
			
		||||
      onSubmit={(onSuccess) => {
 | 
			
		||||
        fieldSchema['x-action-settings']['onSuccess'] = onSuccess;
 | 
			
		||||
        dn.emit('patch', {
 | 
			
		||||
          schema: {
 | 
			
		||||
            ['x-uid']: fieldSchema['x-uid'],
 | 
			
		||||
            'x-action-settings': fieldSchema['x-action-settings'],
 | 
			
		||||
          },
 | 
			
		||||
        });
 | 
			
		||||
      }}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@ -1,51 +0,0 @@
 | 
			
		||||
import { useFieldSchema } from '@formily/react';
 | 
			
		||||
import { SchemaSettingsSwitchItem, useDesignable } from '@nocobase/client';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { useTranslation } from '../locale';
 | 
			
		||||
 | 
			
		||||
// 添加跳转页面选项
 | 
			
		||||
export const SessionSubmit = () => {
 | 
			
		||||
  const { dn } = useDesignable();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  return (
 | 
			
		||||
    <SchemaSettingsSwitchItem
 | 
			
		||||
      title={t('Navigate to new page')}
 | 
			
		||||
      checked={!!fieldSchema?.['x-action-settings']?.sessionSubmit}
 | 
			
		||||
      onChange={(value) => {
 | 
			
		||||
        fieldSchema['x-action-settings'].sessionSubmit = value;
 | 
			
		||||
        dn.emit('patch', {
 | 
			
		||||
          schema: {
 | 
			
		||||
            ['x-uid']: fieldSchema['x-uid'],
 | 
			
		||||
            'x-action-settings': {
 | 
			
		||||
              ...fieldSchema['x-action-settings'],
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        });
 | 
			
		||||
      }}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function SessionUpdate() {
 | 
			
		||||
  const { dn } = useDesignable();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  return (
 | 
			
		||||
    <SchemaSettingsSwitchItem
 | 
			
		||||
      title={t('更新询问')}
 | 
			
		||||
      checked={!!fieldSchema?.['x-action-settings']?.sessionUpdate}
 | 
			
		||||
      onChange={(value) => {
 | 
			
		||||
        fieldSchema['x-action-settings'].sessionUpdate = value;
 | 
			
		||||
        dn.emit('patch', {
 | 
			
		||||
          schema: {
 | 
			
		||||
            ['x-uid']: fieldSchema['x-uid'],
 | 
			
		||||
            'x-action-settings': {
 | 
			
		||||
              ...fieldSchema['x-action-settings'],
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        });
 | 
			
		||||
      }}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@ -1,31 +0,0 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { Popover, Space, Button, Input } from 'antd';
 | 
			
		||||
import { useFieldSchema } from '@formily/react';
 | 
			
		||||
import { useProps } from '@nocobase/client';
 | 
			
		||||
import { ShareAltOutlined } from '@ant-design/icons';
 | 
			
		||||
 | 
			
		||||
export const OutboundButton = (props) => {
 | 
			
		||||
  const { onClick } = useProps(props);
 | 
			
		||||
  const schema = useFieldSchema();
 | 
			
		||||
  const url = window.location.href.split('/', 3).join('/');
 | 
			
		||||
  return (
 | 
			
		||||
    <Popover
 | 
			
		||||
      placement="bottomRight"
 | 
			
		||||
      trigger="click"
 | 
			
		||||
      autoAdjustOverflow
 | 
			
		||||
      content={
 | 
			
		||||
        <Space.Compact style={{ width: '100%' }}>
 | 
			
		||||
          <Input defaultValue={`${url}/r/${schema['x-uid']}`} />
 | 
			
		||||
          <Button type="primary" onClick={onClick}>
 | 
			
		||||
            复制链接
 | 
			
		||||
          </Button>
 | 
			
		||||
        </Space.Compact>
 | 
			
		||||
      }
 | 
			
		||||
    >
 | 
			
		||||
      <Button>
 | 
			
		||||
        <ShareAltOutlined />
 | 
			
		||||
        外链
 | 
			
		||||
      </Button>
 | 
			
		||||
    </Popover>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
@ -1,10 +1,10 @@
 | 
			
		||||
import { SchemaComponent, SchemaSettings, useApp, useDesignable, usePlugin } from '@nocobase/client';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { useTranslation } from '../locale';
 | 
			
		||||
import { useTranslation } from '../../locale';
 | 
			
		||||
import { useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import { Field } from '@nocobase/database';
 | 
			
		||||
import { useCustomComponent } from '../hooks/useCustomComponent';
 | 
			
		||||
import { CustomComponentType } from '../custom-components';
 | 
			
		||||
import { useCustomComponent } from '../../hooks/useCustomComponent';
 | 
			
		||||
import { CustomComponentType } from './custom-components';
 | 
			
		||||
 | 
			
		||||
export const CustomComponentStub = (props) => {
 | 
			
		||||
  return <div>请选择组件</div>;
 | 
			
		||||
@ -2,7 +2,7 @@ import React from 'react';
 | 
			
		||||
import { css } from '@nocobase/client';
 | 
			
		||||
import { Button, Tooltip } from 'antd';
 | 
			
		||||
import { MobileOutlined } from '@ant-design/icons';
 | 
			
		||||
import { useTranslation } from '../locale';
 | 
			
		||||
import { useTranslation } from '../../locale';
 | 
			
		||||
 | 
			
		||||
export const MobileLink = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
@ -2,7 +2,7 @@ import React from 'react';
 | 
			
		||||
import { css, useRequest } from '@nocobase/client';
 | 
			
		||||
import { Badge, Button, Popover, List, message } from 'antd';
 | 
			
		||||
import { BellOutlined } from '@ant-design/icons';
 | 
			
		||||
import { useLinkKey, useInitializationLinkKey } from '../hooks/useNotifications';
 | 
			
		||||
import { useLinkKey, useInitializationLinkKey } from '../../hooks/useNotifications';
 | 
			
		||||
import { useNavigate } from 'react-router-dom';
 | 
			
		||||
import { dayjs } from '@nocobase/utils/client';
 | 
			
		||||
 | 
			
		||||
@ -8,16 +8,9 @@ import {
 | 
			
		||||
  SchemaSettingOptions,
 | 
			
		||||
  useCollection,
 | 
			
		||||
} from '@nocobase/client';
 | 
			
		||||
import { HeraAdminLayout } from './layouts';
 | 
			
		||||
import { remove } from 'lodash';
 | 
			
		||||
import { CalendarBlockInitializer } from './schema-initializer/CalendarBlockInitializer';
 | 
			
		||||
import { MenuDesigner } from './components/ExtendedMenuDesigner';
 | 
			
		||||
import { SessionSubmit, SessionUpdate } from './components/ExtendedActionDesigner';
 | 
			
		||||
import { DetailsPage } from './pages/DetailsPage';
 | 
			
		||||
import { OutboundPage } from './pages/OutboundPage';
 | 
			
		||||
import { HomePageConfiguration } from './pages/HomePageConfiguration';
 | 
			
		||||
import { Configuration } from './components/TokenConfiguration';
 | 
			
		||||
import { HomePage } from './pages/Home';
 | 
			
		||||
import { MenuDesigner } from './schema-components/deprecated/ExtendedMenuDesigner';
 | 
			
		||||
import { InternalPDFViewer } from './schema-components/PDFViewer';
 | 
			
		||||
import {
 | 
			
		||||
  PDFViewerBlockInitializer,
 | 
			
		||||
@ -26,21 +19,20 @@ import {
 | 
			
		||||
  pdfViewActionInitializer,
 | 
			
		||||
  usePDFViewerPrintActionProps,
 | 
			
		||||
} from './schema-initializer/PDFVIewerBlockInitializer';
 | 
			
		||||
import { OutboundButton } from './components/OutboundButton';
 | 
			
		||||
import { useCustomizeUpdateActionProps } from './hooks/useCustomizeUpdateActionProps';
 | 
			
		||||
import { OutboundLinkActionInitializer } from './schema-initializer/OutboundLinkActionInitializer';
 | 
			
		||||
import { OutboundButton, OutboundLinkActionInitializer } from './schema-initializer/OutboundLinkActionInitializer';
 | 
			
		||||
import { CreateSubmitActionInitializer } from './schema-initializer/CreateSubmitActionInitializer';
 | 
			
		||||
import { FilterAssociatedFields } from './schema-initializer/FilterAssociatedFields';
 | 
			
		||||
import { useFieldSchema } from '@formily/react';
 | 
			
		||||
import { isValid } from '@formily/shared';
 | 
			
		||||
import { useCreateActionProps } from './hooks/useCreateActionProps';
 | 
			
		||||
import { PageLayout } from './pages/PageLayout';
 | 
			
		||||
import { useOutboundActionProps } from './hooks/useOutboundActionProps';
 | 
			
		||||
import { AssociatedField } from './components/AssociatedField';
 | 
			
		||||
import { DatePicker } from './schema-components/date-picker';
 | 
			
		||||
import { Page } from './schema-components/page';
 | 
			
		||||
import { SettingBlockInitializer } from './schema-initializer/SettingBlockInitializer';
 | 
			
		||||
import {
 | 
			
		||||
  SessionSubmit,
 | 
			
		||||
  SessionUpdate,
 | 
			
		||||
  EditFormulaTitleField,
 | 
			
		||||
  IsTablePageSize,
 | 
			
		||||
  useFormulaTitleVisible,
 | 
			
		||||
@ -48,12 +40,12 @@ import {
 | 
			
		||||
  EditTitle,
 | 
			
		||||
  SetFilterScope,
 | 
			
		||||
  useSetFilterScopeVisible,
 | 
			
		||||
} from './components/SchemaSettingOptions';
 | 
			
		||||
  AfterSuccess,
 | 
			
		||||
} from './schema-settings';
 | 
			
		||||
import { SignatureInput } from './components/SignatureInput';
 | 
			
		||||
import { RemoteSelect } from './schema-components/remote-select';
 | 
			
		||||
import { Select } from './schema-components/select/Select';
 | 
			
		||||
import { Locale, tval } from './locale';
 | 
			
		||||
import { LinkManager } from './components/LinkManager';
 | 
			
		||||
import {
 | 
			
		||||
  GroupBlockInitializer,
 | 
			
		||||
  GroupBlockProvider,
 | 
			
		||||
@ -71,24 +63,25 @@ import {
 | 
			
		||||
  CustomComponentDispatcher,
 | 
			
		||||
  CustomComponentStub,
 | 
			
		||||
  customComponentDispatcherSettings,
 | 
			
		||||
} from './components/CustomComponentDispatcher';
 | 
			
		||||
} from './components/custom-components/CustomComponentDispatcher';
 | 
			
		||||
import { useFilterBlockActionProps } from './hooks/useFilterBlockActionProps';
 | 
			
		||||
import { AfterSuccess } from './components/Action.Designer';
 | 
			
		||||
import { GroupBlockConfigure } from './components/GroupBlockConfigure/GroupBlockConfigure';
 | 
			
		||||
import { AssociatedFieldInterface } from './interfaces/associated';
 | 
			
		||||
import { CalcFieldInterface } from './interfaces/calc';
 | 
			
		||||
import { CustomFieldInterface } from './interfaces/custom';
 | 
			
		||||
import { CustomAssociatedFieldInterface } from './interfaces/customAssociated';
 | 
			
		||||
import { SignaturePadFieldInterface } from './interfaces/signatureSchema';
 | 
			
		||||
import { CalcResult } from './components/CalcResult';
 | 
			
		||||
import { CustomAssociatedField } from './components/CustomAssociatedField';
 | 
			
		||||
import { CalcResult } from './components/fields/CalcResult';
 | 
			
		||||
import { CustomAssociatedField } from './components/custom-components/CustomAssociatedField';
 | 
			
		||||
import Expression from './components/Expression';
 | 
			
		||||
import { CustomField } from './components/CustomField';
 | 
			
		||||
import { CustomField } from './components/custom-components/CustomField';
 | 
			
		||||
import { useGetCustomAssociatedComponents } from './hooks/useGetCustomAssociatedComponents';
 | 
			
		||||
import { useGetCustomComponents } from './hooks/useGetCustomComponents';
 | 
			
		||||
import { AutoComplete } from './schema-components/AutoComplete/AutoComplete';
 | 
			
		||||
import { AdminLayout, DetailsPage, HomePage, OutboundPage, PageLayout } from './pages';
 | 
			
		||||
import { Configuration, HomePageConfiguration, LinkManager } from './settings-manager-components';
 | 
			
		||||
export { usePDFViewerRef } from './schema-initializer/PDFVIewerBlockInitializer';
 | 
			
		||||
export * from './custom-components';
 | 
			
		||||
export * from './components/custom-components/custom-components';
 | 
			
		||||
 | 
			
		||||
export class PluginCoreClient extends Plugin {
 | 
			
		||||
  locale: Locale;
 | 
			
		||||
@ -157,7 +150,6 @@ export class PluginCoreClient extends Plugin {
 | 
			
		||||
      GroupBlockInitializer,
 | 
			
		||||
      GroupBlockToolbar,
 | 
			
		||||
      GroupBlockProvider,
 | 
			
		||||
      Page,
 | 
			
		||||
      DatePicker,
 | 
			
		||||
      RemoteSelect,
 | 
			
		||||
      SignatureInput,
 | 
			
		||||
@ -165,7 +157,7 @@ export class PluginCoreClient extends Plugin {
 | 
			
		||||
      OutboundLinkActionInitializer,
 | 
			
		||||
      PDFViewerBlockInitializer,
 | 
			
		||||
      PDFViwer: InternalPDFViewer,
 | 
			
		||||
      AdminLayout: HeraAdminLayout,
 | 
			
		||||
      AdminLayout,
 | 
			
		||||
      ExtendedCalendarBlockInitializer: CalendarBlockInitializer,
 | 
			
		||||
      SettingBlock: SettingBlockInitializer,
 | 
			
		||||
      CreateSubmitActionInitializer,
 | 
			
		||||
 | 
			
		||||
@ -26,9 +26,9 @@ import {
 | 
			
		||||
import { Tabs } from 'antd';
 | 
			
		||||
import type { TabsProps } from 'antd';
 | 
			
		||||
import { usePluginVersion } from '../hooks/usePluginVersion';
 | 
			
		||||
import { OnlineUserDropdown } from '../components/OnlineUserProvider';
 | 
			
		||||
import { MobileLink } from '../components/MobileLink';
 | 
			
		||||
import { Notifications } from '../components/Notifications';
 | 
			
		||||
import { OnlineUserDropdown } from '../components/system/OnlineUserProvider';
 | 
			
		||||
import { MobileLink } from '../components/system/MobileLink';
 | 
			
		||||
import { Notifications } from '../components/system/Notifications';
 | 
			
		||||
import { useTranslation } from '../locale';
 | 
			
		||||
 | 
			
		||||
export const useAppSpin = () => {
 | 
			
		||||
@ -451,7 +451,7 @@ const usePageStyle = () => {
 | 
			
		||||
  return useContext(PageStyleContext).style;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const HeraAdminLayout = (props) => {
 | 
			
		||||
export const AdminLayout = (props) => {
 | 
			
		||||
  const { addMenuItem } = useCurrentUserSettingsMenu();
 | 
			
		||||
  const [style, setStyle] = useState('classical');
 | 
			
		||||
  const tabItem = useTabSettings({ style, setStyle });
 | 
			
		||||
@ -8,7 +8,7 @@ import {
 | 
			
		||||
} from '@nocobase/client';
 | 
			
		||||
import { useNavigate, useParams } from 'react-router-dom';
 | 
			
		||||
import pathToRegexp from 'path-to-regexp';
 | 
			
		||||
import { useHeadStyles } from '../pages/style';
 | 
			
		||||
import { useHeadStyles } from './style';
 | 
			
		||||
import { PageHeader as AntdPageHeader } from '@ant-design/pro-layout';
 | 
			
		||||
 | 
			
		||||
export const DetailsPage: React.FC = () => {
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@ import React from 'react';
 | 
			
		||||
import { useStyles } from './style';
 | 
			
		||||
import { Carousel, Image } from 'antd';
 | 
			
		||||
import { useRequest } from '@nocobase/client';
 | 
			
		||||
import { useAppSpin } from '../layouts';
 | 
			
		||||
import { useAppSpin } from './AdminLayout';
 | 
			
		||||
 | 
			
		||||
export const HomePage: React.FC<{}> = () => {
 | 
			
		||||
  const { styles } = useStyles();
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,5 @@
 | 
			
		||||
export * from './AdminLayout';
 | 
			
		||||
export * from './DetailsPage';
 | 
			
		||||
export * from './Home';
 | 
			
		||||
export * from './OutboundPage';
 | 
			
		||||
export * from './PageLayout';
 | 
			
		||||
@ -1,3 +0,0 @@
 | 
			
		||||
export * from './useConfig';
 | 
			
		||||
export * from './usePrefixCls';
 | 
			
		||||
export * from './useToken';
 | 
			
		||||
@ -1,5 +0,0 @@
 | 
			
		||||
import { ConfigProvider } from 'antd';
 | 
			
		||||
import { useContext } from 'react';
 | 
			
		||||
 | 
			
		||||
const { ConfigContext } = ConfigProvider;
 | 
			
		||||
export const useConfig = () => useContext(ConfigContext);
 | 
			
		||||
@ -1,17 +0,0 @@
 | 
			
		||||
import { ConfigProvider } from 'antd';
 | 
			
		||||
import { useContext } from 'react';
 | 
			
		||||
 | 
			
		||||
export const usePrefixCls = (
 | 
			
		||||
  tag?: string,
 | 
			
		||||
  props?: {
 | 
			
		||||
    prefixCls?: string;
 | 
			
		||||
  },
 | 
			
		||||
) => {
 | 
			
		||||
  const { getPrefixCls } = useContext(ConfigProvider.ConfigContext) || {};
 | 
			
		||||
  if ('ConfigContext' in ConfigProvider) {
 | 
			
		||||
    return getPrefixCls?.(tag, props?.prefixCls) || '';
 | 
			
		||||
  } else {
 | 
			
		||||
    const prefix = props?.prefixCls ?? 'ant-';
 | 
			
		||||
    return `${prefix}${tag ?? ''}`;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
@ -1,13 +0,0 @@
 | 
			
		||||
import { theme } from 'antd';
 | 
			
		||||
import { CustomToken } from '../style';
 | 
			
		||||
 | 
			
		||||
interface Result extends ReturnType<typeof theme.useToken> {
 | 
			
		||||
  token: CustomToken;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const useToken = () => {
 | 
			
		||||
  const result = theme.useToken();
 | 
			
		||||
  return result as Result;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export { useToken };
 | 
			
		||||
@ -1,4 +0,0 @@
 | 
			
		||||
export * from './hooks';
 | 
			
		||||
export * from './loading';
 | 
			
		||||
export * from './portal';
 | 
			
		||||
export * from './style';
 | 
			
		||||
@ -1,14 +0,0 @@
 | 
			
		||||
import { message } from 'antd';
 | 
			
		||||
 | 
			
		||||
export const loading = async (title: React.ReactNode = 'Loading...', processor: () => Promise<any>) => {
 | 
			
		||||
  let hide: any = null;
 | 
			
		||||
  const loading = setTimeout(() => {
 | 
			
		||||
    hide = message.loading(title);
 | 
			
		||||
  }, 100);
 | 
			
		||||
  try {
 | 
			
		||||
    return await processor();
 | 
			
		||||
  } finally {
 | 
			
		||||
    hide?.();
 | 
			
		||||
    clearTimeout(loading);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
@ -1,63 +0,0 @@
 | 
			
		||||
import { Observer, ReactFC } from '@formily/react';
 | 
			
		||||
import { observable } from '@formily/reactive';
 | 
			
		||||
import React, { Fragment } from 'react';
 | 
			
		||||
import { createPortal } from 'react-dom';
 | 
			
		||||
import { render as reactRender, unmount as reactUnmount } from './render';
 | 
			
		||||
export interface IPortalProps {
 | 
			
		||||
  id?: string | symbol;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const PortalMap = observable(new Map<string | symbol, React.ReactNode>());
 | 
			
		||||
 | 
			
		||||
export const createPortalProvider = (id: string | symbol) => {
 | 
			
		||||
  const Portal: ReactFC<IPortalProps> = (props) => {
 | 
			
		||||
    if (props.id && !PortalMap.has(props.id)) {
 | 
			
		||||
      PortalMap.set(props.id, null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <Fragment>
 | 
			
		||||
        {props.children}
 | 
			
		||||
        <Observer>
 | 
			
		||||
          {() => {
 | 
			
		||||
            if (!props.id) return <></>;
 | 
			
		||||
            const portal = PortalMap.get(props.id);
 | 
			
		||||
            if (portal) return createPortal(portal, document.body);
 | 
			
		||||
            return <></>;
 | 
			
		||||
          }}
 | 
			
		||||
        </Observer>
 | 
			
		||||
      </Fragment>
 | 
			
		||||
    );
 | 
			
		||||
  };
 | 
			
		||||
  Portal.defaultProps = {
 | 
			
		||||
    id,
 | 
			
		||||
  };
 | 
			
		||||
  return Portal;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function createPortalRoot<T extends React.ReactNode>(host: HTMLElement, id: string) {
 | 
			
		||||
  function render(renderer?: () => T) {
 | 
			
		||||
    if (PortalMap.has(id)) {
 | 
			
		||||
      PortalMap.set(id, renderer?.());
 | 
			
		||||
    } else if (host) {
 | 
			
		||||
      reactRender(<Fragment>{renderer?.()}</Fragment>, host);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function unmount() {
 | 
			
		||||
    if (PortalMap.has(id)) {
 | 
			
		||||
      PortalMap.set(id, null);
 | 
			
		||||
    }
 | 
			
		||||
    if (host) {
 | 
			
		||||
      const unmountResult = reactUnmount(host);
 | 
			
		||||
      if (unmountResult && host.parentNode) {
 | 
			
		||||
        host.parentNode?.removeChild(host);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    render,
 | 
			
		||||
    unmount,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
@ -1,89 +0,0 @@
 | 
			
		||||
import { ReactElement } from 'react';
 | 
			
		||||
import * as ReactDOM from 'react-dom';
 | 
			
		||||
import type { Root } from 'react-dom/client';
 | 
			
		||||
 | 
			
		||||
// 移植自rc-util: https://github.com/react-component/util/blob/master/src/React/render.ts
 | 
			
		||||
 | 
			
		||||
type CreateRoot = (container: ContainerType) => Root;
 | 
			
		||||
 | 
			
		||||
// Let compiler not to search module usage
 | 
			
		||||
const fullClone = {
 | 
			
		||||
  ...ReactDOM,
 | 
			
		||||
} as typeof ReactDOM & {
 | 
			
		||||
  __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED?: {
 | 
			
		||||
    usingClientEntryPoint?: boolean;
 | 
			
		||||
  };
 | 
			
		||||
  createRoot?: CreateRoot;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const { version, render: reactRender, unmountComponentAtNode } = fullClone;
 | 
			
		||||
 | 
			
		||||
let createRoot: CreateRoot;
 | 
			
		||||
try {
 | 
			
		||||
  const mainVersion = Number((version || '').split('.')[0]);
 | 
			
		||||
  if (mainVersion >= 18 && fullClone.createRoot) {
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-var-requires
 | 
			
		||||
    createRoot = fullClone.createRoot;
 | 
			
		||||
  }
 | 
			
		||||
} catch (e) {
 | 
			
		||||
  // Do nothing;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function toggleWarning(skip: boolean) {
 | 
			
		||||
  const { __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED } = fullClone;
 | 
			
		||||
 | 
			
		||||
  if (
 | 
			
		||||
    __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED &&
 | 
			
		||||
    typeof __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED === 'object'
 | 
			
		||||
  ) {
 | 
			
		||||
    __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.usingClientEntryPoint = skip;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const MARK = '__antd_mobile_root__';
 | 
			
		||||
 | 
			
		||||
// ========================== Render ==========================
 | 
			
		||||
type ContainerType = (Element | DocumentFragment) & {
 | 
			
		||||
  [MARK]?: Root;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function legacyRender(node: ReactElement, container: ContainerType) {
 | 
			
		||||
  reactRender(node, container);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function concurrentRender(node: ReactElement, container: ContainerType) {
 | 
			
		||||
  toggleWarning(true);
 | 
			
		||||
  const root = container[MARK] || createRoot(container);
 | 
			
		||||
  toggleWarning(false);
 | 
			
		||||
  root.render(node);
 | 
			
		||||
  container[MARK] = root;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function render(node: ReactElement, container: ContainerType) {
 | 
			
		||||
  if (createRoot as unknown) {
 | 
			
		||||
    concurrentRender(node, container);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  legacyRender(node, container);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ========================== Unmount =========================
 | 
			
		||||
function legacyUnmount(container: ContainerType) {
 | 
			
		||||
  return unmountComponentAtNode(container);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function concurrentUnmount(container: ContainerType) {
 | 
			
		||||
  // Delay to unmount to avoid React 18 sync warning
 | 
			
		||||
  return Promise.resolve().then(() => {
 | 
			
		||||
    container[MARK]?.unmount();
 | 
			
		||||
    delete container[MARK];
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function unmount(container: ContainerType) {
 | 
			
		||||
  if (createRoot as unknown) {
 | 
			
		||||
    return concurrentUnmount(container);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return legacyUnmount(container);
 | 
			
		||||
}
 | 
			
		||||
@ -1,130 +0,0 @@
 | 
			
		||||
import type { CSSInterpolation, CSSObject } from '@ant-design/cssinjs';
 | 
			
		||||
import { useStyleRegister } from '@ant-design/cssinjs';
 | 
			
		||||
import { merge } from '@formily/shared';
 | 
			
		||||
import type { ComponentTokenMap, GlobalToken } from 'antd/es/theme/interface';
 | 
			
		||||
import { AliasToken } from 'antd/es/theme/internal';
 | 
			
		||||
import { useConfig, usePrefixCls, useToken } from './hooks';
 | 
			
		||||
 | 
			
		||||
export interface CustomToken extends AliasToken {
 | 
			
		||||
  /** 顶部导航栏主色 */
 | 
			
		||||
  colorPrimaryHeader: string;
 | 
			
		||||
  /** 导航栏背景色 */
 | 
			
		||||
  colorBgHeader: string;
 | 
			
		||||
  /** 导航栏菜单背景色悬浮态 */
 | 
			
		||||
  colorBgHeaderMenuHover: string;
 | 
			
		||||
  /** 导航栏菜单背景色激活态 */
 | 
			
		||||
  colorBgHeaderMenuActive: string;
 | 
			
		||||
  /** 导航栏菜单文本色 */
 | 
			
		||||
  colorTextHeaderMenu: string;
 | 
			
		||||
  /** 导航栏菜单文本色悬浮态 */
 | 
			
		||||
  colorTextHeaderMenuHover: string;
 | 
			
		||||
  /** 导航栏菜单文本色激活态 */
 | 
			
		||||
  colorTextHeaderMenuActive: string;
 | 
			
		||||
 | 
			
		||||
  /** UI 配置色 */
 | 
			
		||||
  colorSettings: string;
 | 
			
		||||
  /** 鼠标悬浮时显示的背景色 */
 | 
			
		||||
  colorBgSettingsHover: string;
 | 
			
		||||
  /** 鼠标悬浮时显示的边框色 */
 | 
			
		||||
  colorBorderSettingsHover: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type OverrideComponent = keyof ComponentTokenMap | string;
 | 
			
		||||
 | 
			
		||||
export interface StyleInfo {
 | 
			
		||||
  hashId: string;
 | 
			
		||||
  prefixCls: string;
 | 
			
		||||
  rootPrefixCls: string;
 | 
			
		||||
  iconPrefixCls: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type TokenWithCommonCls<T> = T & {
 | 
			
		||||
  /** Wrap component class with `.` prefix */
 | 
			
		||||
  componentCls: string;
 | 
			
		||||
  /** Origin prefix which do not have `.` prefix */
 | 
			
		||||
  prefixCls: string;
 | 
			
		||||
  /** Wrap icon class with `.` prefix */
 | 
			
		||||
  iconCls: string;
 | 
			
		||||
  /** Wrap ant prefixCls class with `.` prefix */
 | 
			
		||||
  antCls: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type GenerateStyle<
 | 
			
		||||
  ComponentToken extends object = TokenWithCommonCls<GlobalToken>,
 | 
			
		||||
  ReturnType = CSSInterpolation,
 | 
			
		||||
> = (token: ComponentToken, options?: any) => ReturnType;
 | 
			
		||||
 | 
			
		||||
export const genCommonStyle = (token: any, componentPrefixCls: string): CSSObject => {
 | 
			
		||||
  const { fontFamily, fontSize } = token;
 | 
			
		||||
 | 
			
		||||
  const rootPrefixSelector = `[class^="${componentPrefixCls}"], [class*=" ${componentPrefixCls}"]`;
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    [rootPrefixSelector]: {
 | 
			
		||||
      fontFamily,
 | 
			
		||||
      fontSize,
 | 
			
		||||
      boxSizing: 'border-box',
 | 
			
		||||
 | 
			
		||||
      '&::before, &::after': {
 | 
			
		||||
        boxSizing: 'border-box',
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      [rootPrefixSelector]: {
 | 
			
		||||
        boxSizing: 'border-box',
 | 
			
		||||
 | 
			
		||||
        '&::before, &::after': {
 | 
			
		||||
          boxSizing: 'border-box',
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
export type UseComponentStyleResult = {
 | 
			
		||||
  wrapSSR: ReturnType<typeof useStyleRegister>;
 | 
			
		||||
  hashId: string;
 | 
			
		||||
  componentCls: string;
 | 
			
		||||
  rootPrefixCls: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const genStyleHook = <ComponentName extends OverrideComponent>(
 | 
			
		||||
  component: ComponentName,
 | 
			
		||||
  styleFn: (token: TokenWithCommonCls<CustomToken>, props: any, info: StyleInfo) => CSSInterpolation,
 | 
			
		||||
) => {
 | 
			
		||||
  return (props?: any): UseComponentStyleResult => {
 | 
			
		||||
    const { theme, token, hashId } = useToken();
 | 
			
		||||
    const { getPrefixCls, iconPrefixCls } = useConfig();
 | 
			
		||||
    const prefixCls = usePrefixCls(component);
 | 
			
		||||
    const rootPrefixCls = getPrefixCls();
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      wrapSSR: useStyleRegister(
 | 
			
		||||
        {
 | 
			
		||||
          theme: theme as any,
 | 
			
		||||
          token,
 | 
			
		||||
          hashId,
 | 
			
		||||
          path: ['formily-antd', component, prefixCls, iconPrefixCls],
 | 
			
		||||
        },
 | 
			
		||||
        () => {
 | 
			
		||||
          const componentCls = `.${prefixCls}`;
 | 
			
		||||
          const mergedToken: TokenWithCommonCls<CustomToken> = merge(token, {
 | 
			
		||||
            componentCls,
 | 
			
		||||
            prefixCls,
 | 
			
		||||
            iconCls: `.${iconPrefixCls}`,
 | 
			
		||||
            antCls: `.${rootPrefixCls}`,
 | 
			
		||||
          });
 | 
			
		||||
 | 
			
		||||
          const styleInterpolation = styleFn(mergedToken, props, {
 | 
			
		||||
            hashId,
 | 
			
		||||
            prefixCls,
 | 
			
		||||
            rootPrefixCls,
 | 
			
		||||
            iconPrefixCls,
 | 
			
		||||
          });
 | 
			
		||||
          return [genCommonStyle(token, prefixCls), styleInterpolation];
 | 
			
		||||
        },
 | 
			
		||||
      ),
 | 
			
		||||
      hashId,
 | 
			
		||||
      componentCls: prefixCls,
 | 
			
		||||
      rootPrefixCls,
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
@ -1,18 +0,0 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { render, screen } from 'testUtils';
 | 
			
		||||
import App1 from '../demos/demo1';
 | 
			
		||||
 | 
			
		||||
describe('ErrorFallback', () => {
 | 
			
		||||
  it('should render correctly', () => {
 | 
			
		||||
    render(<App1 />);
 | 
			
		||||
 | 
			
		||||
    expect(screen.getByText(/render failed/i)).toBeInTheDocument();
 | 
			
		||||
    expect(screen.getByText(/this is likely a nocobase internals bug\. please open an issue at/i)).toBeInTheDocument();
 | 
			
		||||
    expect(screen.getByRole('link', { name: /feedback/i })).toBeInTheDocument();
 | 
			
		||||
    expect(screen.getByText(/try again/i)).toBeInTheDocument();
 | 
			
		||||
    expect(screen.getByText(/error: error message/i)).toBeInTheDocument();
 | 
			
		||||
 | 
			
		||||
    // 底部复制按钮
 | 
			
		||||
    expect(document.querySelector('.ant-typography-copy')).toBeInTheDocument();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@ -1,15 +0,0 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { ErrorBoundary } from 'react-error-boundary';
 | 
			
		||||
import { ErrorFallback } from '../ErrorFallback';
 | 
			
		||||
 | 
			
		||||
const App = () => {
 | 
			
		||||
  throw new Error('error message');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default () => {
 | 
			
		||||
  return (
 | 
			
		||||
    <ErrorBoundary FallbackComponent={ErrorFallback} onError={console.error}>
 | 
			
		||||
      <App />
 | 
			
		||||
    </ErrorBoundary>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
@ -1,10 +0,0 @@
 | 
			
		||||
---
 | 
			
		||||
group:
 | 
			
		||||
  title: Schema Components
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
# ErrorFallback
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
 | 
			
		||||
<code src="./demos/demo1.tsx"></code>
 | 
			
		||||
@ -1 +0,0 @@
 | 
			
		||||
export * from './ErrorFallback';
 | 
			
		||||
@ -1,145 +0,0 @@
 | 
			
		||||
import { css } from '@emotion/css';
 | 
			
		||||
import { useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import React, { useContext, useEffect, useRef, useState } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { useRecord, useDesignable, SchemaSettingsSwitchItem } from '@nocobase/client';
 | 
			
		||||
import { useIsBlockInPage } from './hooks/useIsBlockInPage';
 | 
			
		||||
 | 
			
		||||
const FixedBlockContext = React.createContext<{
 | 
			
		||||
  setFixedBlock: (value: string | false) => void;
 | 
			
		||||
  height: number | string;
 | 
			
		||||
  fixedBlockUID: boolean | string;
 | 
			
		||||
  fixedBlockUIDRef: React.MutableRefObject<boolean | string>;
 | 
			
		||||
  inFixedBlock: boolean;
 | 
			
		||||
}>({
 | 
			
		||||
  setFixedBlock: () => {},
 | 
			
		||||
  height: 0,
 | 
			
		||||
  fixedBlockUID: false,
 | 
			
		||||
  fixedBlockUIDRef: { current: false },
 | 
			
		||||
  inFixedBlock: false,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const useFixedSchema = () => {
 | 
			
		||||
  const field = useField();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const { setFixedBlock, fixedBlockUID, fixedBlockUIDRef } = useFixedBlock();
 | 
			
		||||
  const hasSet = useRef(false);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!fixedBlockUIDRef.current || hasSet.current) {
 | 
			
		||||
      setFixedBlock(field?.decoratorProps?.fixedBlock ? fieldSchema['x-uid'] : false);
 | 
			
		||||
      hasSet.current = true;
 | 
			
		||||
    }
 | 
			
		||||
  }, [field?.decoratorProps?.fixedBlock]);
 | 
			
		||||
 | 
			
		||||
  return fieldSchema['x-uid'] === fixedBlockUID;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const useFixedBlock = () => {
 | 
			
		||||
  return useContext(FixedBlockContext);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const FixedBlockWrapper: React.FC = (props) => {
 | 
			
		||||
  const fixedBlock = useFixedSchema();
 | 
			
		||||
  const { height, fixedBlockUID } = useFixedBlock();
 | 
			
		||||
  const record = useRecord();
 | 
			
		||||
  const isPopup = Object.keys(record).length;
 | 
			
		||||
  if (isPopup) {
 | 
			
		||||
    return <>{props.children}</>;
 | 
			
		||||
  }
 | 
			
		||||
  /**
 | 
			
		||||
   * The fixedBlockUID of false means that the page has no fixed blocks
 | 
			
		||||
   * isPopup means that the FixedBlock is in the popup mode
 | 
			
		||||
   */
 | 
			
		||||
  if (!fixedBlock && fixedBlockUID) return null;
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      className="nb-fixed-block"
 | 
			
		||||
      style={{
 | 
			
		||||
        height: fixedBlockUID ? `calc(100vh - ${height})` : undefined,
 | 
			
		||||
      }}
 | 
			
		||||
    >
 | 
			
		||||
      {props.children}
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const FixedBlockDesignerItem = () => {
 | 
			
		||||
  const field = useField();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const { dn } = useDesignable();
 | 
			
		||||
  const { inFixedBlock } = useFixedBlock();
 | 
			
		||||
  const { isBlockInPage } = useIsBlockInPage();
 | 
			
		||||
 | 
			
		||||
  if (!isBlockInPage() || !inFixedBlock) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
  return (
 | 
			
		||||
    <SchemaSettingsSwitchItem
 | 
			
		||||
      title={t('Fix block')}
 | 
			
		||||
      checked={fieldSchema['x-decorator-props']?.fixedBlock}
 | 
			
		||||
      onChange={async (fixedBlock) => {
 | 
			
		||||
        const decoratorProps = {
 | 
			
		||||
          ...fieldSchema['x-decorator-props'],
 | 
			
		||||
          fixedBlock,
 | 
			
		||||
        };
 | 
			
		||||
        await dn.emit('patch', {
 | 
			
		||||
          schema: {
 | 
			
		||||
            ['x-uid']: fieldSchema['x-uid'],
 | 
			
		||||
            'x-decorator-props': decoratorProps,
 | 
			
		||||
          },
 | 
			
		||||
        });
 | 
			
		||||
        field.decoratorProps = fieldSchema['x-decorator-props'] = decoratorProps;
 | 
			
		||||
      }}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface FixedBlockProps {
 | 
			
		||||
  height: number | string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const fixedBlockCss = css`
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  .noco-card-item {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    .ant-card {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      flex-direction: column;
 | 
			
		||||
      height: 100%;
 | 
			
		||||
      .ant-card-body {
 | 
			
		||||
        height: 1px;
 | 
			
		||||
        flex: 1;
 | 
			
		||||
        display: flex;
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const FixedBlock: React.FC<FixedBlockProps> = (props) => {
 | 
			
		||||
  const { height } = props;
 | 
			
		||||
  const [fixedBlockUID, _setFixedBlock] = useState<false | string>(false);
 | 
			
		||||
  const fixedBlockUIDRef = useRef(fixedBlockUID);
 | 
			
		||||
  const setFixedBlock = (v) => {
 | 
			
		||||
    fixedBlockUIDRef.current = v;
 | 
			
		||||
    _setFixedBlock(v);
 | 
			
		||||
  };
 | 
			
		||||
  return (
 | 
			
		||||
    <FixedBlockContext.Provider value={{ inFixedBlock: true, height, setFixedBlock, fixedBlockUID, fixedBlockUIDRef }}>
 | 
			
		||||
      <div
 | 
			
		||||
        className={fixedBlockUID ? fixedBlockCss : ''}
 | 
			
		||||
        style={{
 | 
			
		||||
          height: fixedBlockUID ? `calc(100vh - ${height})` : undefined,
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        {props.children}
 | 
			
		||||
      </div>
 | 
			
		||||
    </FixedBlockContext.Provider>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default FixedBlock;
 | 
			
		||||
@ -1,139 +0,0 @@
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { ISchema, useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import { SchemaSettings, useDesignable, useSchemaToolbar } from '@nocobase/client';
 | 
			
		||||
 | 
			
		||||
function useNotDisableHeader() {
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  return !fieldSchema['x-component-props']?.disablePageHeader;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const pageSettings = new SchemaSettings({
 | 
			
		||||
  name: 'PageSettings',
 | 
			
		||||
  items: [
 | 
			
		||||
    {
 | 
			
		||||
      name: 'enablePageHeader',
 | 
			
		||||
      type: 'switch',
 | 
			
		||||
      useComponentProps() {
 | 
			
		||||
        const { dn } = useDesignable();
 | 
			
		||||
        const { t } = useTranslation();
 | 
			
		||||
        const fieldSchema = useFieldSchema();
 | 
			
		||||
        return {
 | 
			
		||||
          title: t('Enable page header'),
 | 
			
		||||
          checked: !fieldSchema['x-component-props']?.disablePageHeader,
 | 
			
		||||
          onChange(v) {
 | 
			
		||||
            fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
 | 
			
		||||
            fieldSchema['x-component-props']['disablePageHeader'] = !v;
 | 
			
		||||
            dn.emit('patch', {
 | 
			
		||||
              schema: {
 | 
			
		||||
                ['x-uid']: fieldSchema['x-uid'],
 | 
			
		||||
                ['x-component-props']: fieldSchema['x-component-props'],
 | 
			
		||||
              },
 | 
			
		||||
            });
 | 
			
		||||
            dn.refresh();
 | 
			
		||||
          },
 | 
			
		||||
        };
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: 'divider',
 | 
			
		||||
      type: 'divider',
 | 
			
		||||
      useVisible: useNotDisableHeader,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: 'displayPageTitle',
 | 
			
		||||
      type: 'switch',
 | 
			
		||||
      useVisible: useNotDisableHeader,
 | 
			
		||||
      useComponentProps() {
 | 
			
		||||
        const { dn } = useDesignable();
 | 
			
		||||
        const { t } = useTranslation();
 | 
			
		||||
        const fieldSchema = useFieldSchema();
 | 
			
		||||
        return {
 | 
			
		||||
          title: t('Display page title'),
 | 
			
		||||
          checked: !fieldSchema['x-component-props']?.hidePageTitle,
 | 
			
		||||
          onChange(v) {
 | 
			
		||||
            fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
 | 
			
		||||
            fieldSchema['x-component-props']['hidePageTitle'] = !v;
 | 
			
		||||
            dn.emit('patch', {
 | 
			
		||||
              schema: {
 | 
			
		||||
                ['x-uid']: fieldSchema['x-uid'],
 | 
			
		||||
                ['x-component-props']: fieldSchema['x-component-props'],
 | 
			
		||||
              },
 | 
			
		||||
            });
 | 
			
		||||
            dn.refresh();
 | 
			
		||||
          },
 | 
			
		||||
        };
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: 'editPageTitle',
 | 
			
		||||
      type: 'modal',
 | 
			
		||||
      useVisible() {
 | 
			
		||||
        const notDisablePageHeader = useNotDisableHeader();
 | 
			
		||||
        const fieldSchema = useFieldSchema();
 | 
			
		||||
        const notHidePageTitle = !fieldSchema['x-component-props']?.hidePageTitle;
 | 
			
		||||
 | 
			
		||||
        return notDisablePageHeader && notHidePageTitle;
 | 
			
		||||
      },
 | 
			
		||||
      useComponentProps() {
 | 
			
		||||
        const { dn } = useDesignable();
 | 
			
		||||
        const { t } = useTranslation();
 | 
			
		||||
        const field = useField();
 | 
			
		||||
        const fieldSchema = useFieldSchema();
 | 
			
		||||
        const { title } = useSchemaToolbar();
 | 
			
		||||
        return {
 | 
			
		||||
          hide: true,
 | 
			
		||||
          title: t('Edit page title'),
 | 
			
		||||
          schema: {
 | 
			
		||||
            type: 'object',
 | 
			
		||||
            title: t('Edit page title'),
 | 
			
		||||
            properties: {
 | 
			
		||||
              title: {
 | 
			
		||||
                title: t('Title'),
 | 
			
		||||
                required: true,
 | 
			
		||||
                'x-decorator': 'FormItem',
 | 
			
		||||
                'x-component': 'Input',
 | 
			
		||||
                'x-component-props': {},
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
          } as ISchema,
 | 
			
		||||
          initialValues: { title },
 | 
			
		||||
          onSubmit({ title }) {
 | 
			
		||||
            field.title = title;
 | 
			
		||||
            fieldSchema['title'] = title;
 | 
			
		||||
            dn.emit('patch', {
 | 
			
		||||
              schema: {
 | 
			
		||||
                ['x-uid']: fieldSchema['x-uid'],
 | 
			
		||||
                title,
 | 
			
		||||
              },
 | 
			
		||||
            });
 | 
			
		||||
          },
 | 
			
		||||
        };
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: 'enablePageTabs',
 | 
			
		||||
      type: 'switch',
 | 
			
		||||
      useVisible: useNotDisableHeader,
 | 
			
		||||
      useComponentProps() {
 | 
			
		||||
        const { dn } = useDesignable();
 | 
			
		||||
        const { t } = useTranslation();
 | 
			
		||||
        const fieldSchema = useFieldSchema();
 | 
			
		||||
        return {
 | 
			
		||||
          title: t('Enable page tabs'),
 | 
			
		||||
          checked: fieldSchema['x-component-props']?.enablePageTabs,
 | 
			
		||||
          onChange(v) {
 | 
			
		||||
            fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
 | 
			
		||||
            fieldSchema['x-component-props']['enablePageTabs'] = v;
 | 
			
		||||
            dn.emit('patch', {
 | 
			
		||||
              schema: {
 | 
			
		||||
                ['x-uid']: fieldSchema['x-uid'],
 | 
			
		||||
                ['x-component-props']: fieldSchema['x-component-props'],
 | 
			
		||||
              },
 | 
			
		||||
            });
 | 
			
		||||
            dn.refresh();
 | 
			
		||||
          },
 | 
			
		||||
        };
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
});
 | 
			
		||||
@ -1,235 +0,0 @@
 | 
			
		||||
import { PlusOutlined } from '@ant-design/icons';
 | 
			
		||||
import { PageHeader as AntdPageHeader } from '@ant-design/pro-layout';
 | 
			
		||||
import { FormLayout } from '@formily/antd-v5';
 | 
			
		||||
import { Schema, SchemaOptionsContext, useFieldSchema } from '@formily/react';
 | 
			
		||||
import { Button, Tabs } from 'antd';
 | 
			
		||||
import classNames from 'classnames';
 | 
			
		||||
import React, { useContext, useEffect, useMemo, useState } from 'react';
 | 
			
		||||
import { ErrorBoundary } from 'react-error-boundary';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { useSearchParams } from 'react-router-dom';
 | 
			
		||||
import FixedBlock from './FixedBlock';
 | 
			
		||||
import { PageDesigner, PageTabDesigner } from './PageTabDesigner';
 | 
			
		||||
import { useAClStyles, useStyles } from './style';
 | 
			
		||||
import {
 | 
			
		||||
  DndContext,
 | 
			
		||||
  FilterBlockProvider,
 | 
			
		||||
  FormDialog,
 | 
			
		||||
  Icon,
 | 
			
		||||
  SchemaComponent,
 | 
			
		||||
  SchemaComponentOptions,
 | 
			
		||||
  SortableItem,
 | 
			
		||||
  useCompile,
 | 
			
		||||
  useDesignable,
 | 
			
		||||
  useDocumentTitle,
 | 
			
		||||
  useGlobalTheme,
 | 
			
		||||
  useToken,
 | 
			
		||||
} from '@nocobase/client';
 | 
			
		||||
import { useAppSpin } from '../../layouts';
 | 
			
		||||
import { useGetAriaLabelOfSchemaInitializer } from './hooks/useGetAriaLabelOfSchemaInitializer';
 | 
			
		||||
import { ErrorFallback } from '../error-fallback';
 | 
			
		||||
 | 
			
		||||
export const Page = (props) => {
 | 
			
		||||
  const { children, ...others } = props;
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const compile = useCompile();
 | 
			
		||||
  const { title, setTitle } = useDocumentTitle();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const dn = useDesignable();
 | 
			
		||||
  const { theme } = useGlobalTheme();
 | 
			
		||||
  const { getAriaLabel } = useGetAriaLabelOfSchemaInitializer();
 | 
			
		||||
 | 
			
		||||
  // react18  tab 动画会卡顿,所以第一个 tab 时,动画禁用,后面的 tab 才启用
 | 
			
		||||
  const [hasMounted, setHasMounted] = useState(false);
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      setHasMounted(true);
 | 
			
		||||
    });
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!title) {
 | 
			
		||||
      setTitle(t(fieldSchema.title));
 | 
			
		||||
    }
 | 
			
		||||
  }, [fieldSchema.title, title]);
 | 
			
		||||
  const disablePageHeader = fieldSchema['x-component-props']?.disablePageHeader;
 | 
			
		||||
  const enablePageTabs = fieldSchema['x-component-props']?.enablePageTabs;
 | 
			
		||||
  const hidePageTitle = fieldSchema['x-component-props']?.hidePageTitle;
 | 
			
		||||
  const options = useContext(SchemaOptionsContext);
 | 
			
		||||
  const [searchParams, setSearchParams] = useSearchParams();
 | 
			
		||||
  const [loading, setLoading] = useState(false);
 | 
			
		||||
  const activeKey = useMemo(
 | 
			
		||||
    () => searchParams.get('tab') || Object.keys(fieldSchema.properties || {}).shift(),
 | 
			
		||||
    [fieldSchema.properties, searchParams],
 | 
			
		||||
  );
 | 
			
		||||
  const [height, setHeight] = useState(0);
 | 
			
		||||
  const { wrapSSR, hashId, componentCls } = useStyles();
 | 
			
		||||
  const aclStyles = useAClStyles();
 | 
			
		||||
 | 
			
		||||
  const handleErrors = (error) => {
 | 
			
		||||
    console.error(error);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const pageHeaderTitle = hidePageTitle ? undefined : fieldSchema.title || compile(title);
 | 
			
		||||
  return wrapSSR(
 | 
			
		||||
    <FilterBlockProvider>
 | 
			
		||||
      <div className={`${componentCls} ${hashId} ${aclStyles.styles}`}>
 | 
			
		||||
        <PageDesigner title={fieldSchema.title || title} />
 | 
			
		||||
        <div
 | 
			
		||||
          ref={(ref) => {
 | 
			
		||||
            setHeight(Math.floor(ref?.getBoundingClientRect().height || 0) + 1);
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          {!disablePageHeader && (
 | 
			
		||||
            <AntdPageHeader
 | 
			
		||||
              className={classNames('pageHeaderCss', pageHeaderTitle || enablePageTabs ? '' : 'height0')}
 | 
			
		||||
              ghost={false}
 | 
			
		||||
              // 如果标题为空的时候会导致 PageHeader 不渲染,所以这里设置一个空白字符,然后再设置高度为 0
 | 
			
		||||
              title={pageHeaderTitle || ' '}
 | 
			
		||||
              {...others}
 | 
			
		||||
              footer={
 | 
			
		||||
                enablePageTabs && (
 | 
			
		||||
                  <DndContext>
 | 
			
		||||
                    <Tabs
 | 
			
		||||
                      size={'small'}
 | 
			
		||||
                      animated={hasMounted}
 | 
			
		||||
                      activeKey={activeKey}
 | 
			
		||||
                      onTabClick={(activeKey) => {
 | 
			
		||||
                        setLoading(true);
 | 
			
		||||
                        setSearchParams([['tab', activeKey]]);
 | 
			
		||||
                        setTimeout(() => {
 | 
			
		||||
                          setLoading(false);
 | 
			
		||||
                        }, 50);
 | 
			
		||||
                      }}
 | 
			
		||||
                      tabBarExtraContent={
 | 
			
		||||
                        dn.designable && (
 | 
			
		||||
                          <Button
 | 
			
		||||
                            aria-label={getAriaLabel('tabs')}
 | 
			
		||||
                            icon={<PlusOutlined />}
 | 
			
		||||
                            className={'addTabBtn'}
 | 
			
		||||
                            type={'dashed'}
 | 
			
		||||
                            onClick={async () => {
 | 
			
		||||
                              const values = await FormDialog(
 | 
			
		||||
                                t('Add tab'),
 | 
			
		||||
                                () => {
 | 
			
		||||
                                  return (
 | 
			
		||||
                                    <SchemaComponentOptions
 | 
			
		||||
                                      scope={options.scope}
 | 
			
		||||
                                      components={{ ...options.components }}
 | 
			
		||||
                                    >
 | 
			
		||||
                                      <FormLayout layout={'vertical'}>
 | 
			
		||||
                                        <SchemaComponent
 | 
			
		||||
                                          schema={{
 | 
			
		||||
                                            properties: {
 | 
			
		||||
                                              title: {
 | 
			
		||||
                                                title: t('Tab name'),
 | 
			
		||||
                                                'x-component': 'Input',
 | 
			
		||||
                                                'x-decorator': 'FormItem',
 | 
			
		||||
                                                required: true,
 | 
			
		||||
                                              },
 | 
			
		||||
                                              icon: {
 | 
			
		||||
                                                title: t('Icon'),
 | 
			
		||||
                                                'x-component': 'IconPicker',
 | 
			
		||||
                                                'x-decorator': 'FormItem',
 | 
			
		||||
                                              },
 | 
			
		||||
                                            },
 | 
			
		||||
                                          }}
 | 
			
		||||
                                        />
 | 
			
		||||
                                      </FormLayout>
 | 
			
		||||
                                    </SchemaComponentOptions>
 | 
			
		||||
                                  );
 | 
			
		||||
                                },
 | 
			
		||||
                                theme,
 | 
			
		||||
                              ).open({
 | 
			
		||||
                                initialValues: {},
 | 
			
		||||
                              });
 | 
			
		||||
                              const { title, icon } = values;
 | 
			
		||||
                              dn.insertBeforeEnd({
 | 
			
		||||
                                type: 'void',
 | 
			
		||||
                                title,
 | 
			
		||||
                                'x-icon': icon,
 | 
			
		||||
                                'x-component': 'Grid',
 | 
			
		||||
                                'x-initializer': 'BlockInitializers',
 | 
			
		||||
                                properties: {},
 | 
			
		||||
                              });
 | 
			
		||||
                            }}
 | 
			
		||||
                          >
 | 
			
		||||
                            {t('Add tab')}
 | 
			
		||||
                          </Button>
 | 
			
		||||
                        )
 | 
			
		||||
                      }
 | 
			
		||||
                      items={fieldSchema.mapProperties((schema) => {
 | 
			
		||||
                        return {
 | 
			
		||||
                          label: (
 | 
			
		||||
                            <SortableItem
 | 
			
		||||
                              id={schema.name as string}
 | 
			
		||||
                              schema={schema}
 | 
			
		||||
                              className={classNames('nb-action-link', 'designerCss', props.className)}
 | 
			
		||||
                            >
 | 
			
		||||
                              {schema['x-icon'] && <Icon style={{ marginRight: 8 }} type={schema['x-icon']} />}
 | 
			
		||||
                              <span>{schema.title || t('Unnamed')}</span>
 | 
			
		||||
                              <PageTabDesigner schema={schema} />
 | 
			
		||||
                            </SortableItem>
 | 
			
		||||
                          ),
 | 
			
		||||
                          key: schema.name as string,
 | 
			
		||||
                        };
 | 
			
		||||
                      })}
 | 
			
		||||
                    />
 | 
			
		||||
                  </DndContext>
 | 
			
		||||
                )
 | 
			
		||||
              }
 | 
			
		||||
            />
 | 
			
		||||
          )}
 | 
			
		||||
        </div>
 | 
			
		||||
        <div className="nb-page-wrapper">
 | 
			
		||||
          <ErrorBoundary FallbackComponent={ErrorFallback} onError={handleErrors}>
 | 
			
		||||
            {PageContent(loading, disablePageHeader, enablePageTabs, fieldSchema, activeKey, height, props)}
 | 
			
		||||
          </ErrorBoundary>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </FilterBlockProvider>,
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Page.displayName = 'Page';
 | 
			
		||||
 | 
			
		||||
function PageContent(
 | 
			
		||||
  loading: boolean,
 | 
			
		||||
  disablePageHeader: any,
 | 
			
		||||
  enablePageTabs: any,
 | 
			
		||||
  fieldSchema: Schema<any, any, any, any, any, any, any, any, any>,
 | 
			
		||||
  activeKey: string,
 | 
			
		||||
  height: number,
 | 
			
		||||
  props: any,
 | 
			
		||||
): React.ReactNode {
 | 
			
		||||
  const { token } = useToken();
 | 
			
		||||
  const { render } = useAppSpin();
 | 
			
		||||
 | 
			
		||||
  if (loading) {
 | 
			
		||||
    return render();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return !disablePageHeader && enablePageTabs ? (
 | 
			
		||||
    fieldSchema.mapProperties((schema) => {
 | 
			
		||||
      if (schema.name !== activeKey) return null;
 | 
			
		||||
 | 
			
		||||
      return (
 | 
			
		||||
        <FixedBlock key={schema.name} height={`calc(${height}px + 46px + ${token.marginLG}px * 2)`}>
 | 
			
		||||
          <SchemaComponent
 | 
			
		||||
            schema={
 | 
			
		||||
              new Schema({
 | 
			
		||||
                properties: {
 | 
			
		||||
                  [schema.name]: schema,
 | 
			
		||||
                },
 | 
			
		||||
              })
 | 
			
		||||
            }
 | 
			
		||||
          />
 | 
			
		||||
        </FixedBlock>
 | 
			
		||||
      );
 | 
			
		||||
    })
 | 
			
		||||
  ) : (
 | 
			
		||||
    <FixedBlock height={`calc(${height}px + 46px + ${token.marginLG}px * 2)`}>
 | 
			
		||||
      <div className={`pageWithFixedBlockCss nb-page-content`}>{props.children}</div>
 | 
			
		||||
    </FixedBlock>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@ -1,83 +0,0 @@
 | 
			
		||||
import { App } from 'antd';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { ISchema } from '@formily/json-schema';
 | 
			
		||||
import { useDesignable, useSchemaToolbar, SchemaSettings } from '@nocobase/client';
 | 
			
		||||
 | 
			
		||||
export const pageTabSettings = new SchemaSettings({
 | 
			
		||||
  name: 'PageTabSettings',
 | 
			
		||||
  items: [
 | 
			
		||||
    {
 | 
			
		||||
      name: 'edit',
 | 
			
		||||
      type: 'modal',
 | 
			
		||||
      useComponentProps() {
 | 
			
		||||
        const { t } = useTranslation();
 | 
			
		||||
        const { schema } = useSchemaToolbar<{ schema: ISchema }>();
 | 
			
		||||
        const { dn } = useDesignable();
 | 
			
		||||
        return {
 | 
			
		||||
          title: t('Edit'),
 | 
			
		||||
          schema: {
 | 
			
		||||
            type: 'object',
 | 
			
		||||
            title: t('Edit tab'),
 | 
			
		||||
            properties: {
 | 
			
		||||
              title: {
 | 
			
		||||
                title: t('Tab name'),
 | 
			
		||||
                required: true,
 | 
			
		||||
                'x-decorator': 'FormItem',
 | 
			
		||||
                'x-component': 'Input',
 | 
			
		||||
                'x-component-props': {},
 | 
			
		||||
              },
 | 
			
		||||
              icon: {
 | 
			
		||||
                title: t('Icon'),
 | 
			
		||||
                'x-decorator': 'FormItem',
 | 
			
		||||
                'x-component': 'IconPicker',
 | 
			
		||||
                'x-component-props': {},
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
          } as ISchema,
 | 
			
		||||
          initialValues: { title: schema.title, icon: schema['x-icon'] },
 | 
			
		||||
          onSubmit: ({ title, icon }) => {
 | 
			
		||||
            schema.title = title;
 | 
			
		||||
            schema['x-icon'] = icon;
 | 
			
		||||
            dn.emit('patch', {
 | 
			
		||||
              schema: {
 | 
			
		||||
                ['x-uid']: schema['x-uid'],
 | 
			
		||||
                title,
 | 
			
		||||
                'x-icon': icon,
 | 
			
		||||
              },
 | 
			
		||||
            });
 | 
			
		||||
            dn.refresh();
 | 
			
		||||
          },
 | 
			
		||||
        };
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: 'divider',
 | 
			
		||||
      type: 'divider',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: 'delete',
 | 
			
		||||
      type: 'item',
 | 
			
		||||
      useComponentProps() {
 | 
			
		||||
        const { modal } = App.useApp();
 | 
			
		||||
        const { dn } = useDesignable();
 | 
			
		||||
        const { t } = useTranslation();
 | 
			
		||||
        const { schema } = useSchemaToolbar();
 | 
			
		||||
        return {
 | 
			
		||||
          title: 'Delete',
 | 
			
		||||
          eventKey: 'remove',
 | 
			
		||||
          onClick() {
 | 
			
		||||
            modal.confirm({
 | 
			
		||||
              title: t('Delete block'),
 | 
			
		||||
              content: t('Are you sure you want to delete it?'),
 | 
			
		||||
              ...confirm,
 | 
			
		||||
              onOk() {
 | 
			
		||||
                dn.remove(schema);
 | 
			
		||||
              },
 | 
			
		||||
            });
 | 
			
		||||
          },
 | 
			
		||||
          children: t('Delete'),
 | 
			
		||||
        };
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
});
 | 
			
		||||
@ -1,56 +0,0 @@
 | 
			
		||||
import { DragOutlined } from '@ant-design/icons';
 | 
			
		||||
import { useFieldSchema } from '@formily/react';
 | 
			
		||||
import { DragHandler, SchemaToolbarProvider, useDesignable, useSchemaSettingsRender } from '@nocobase/client';
 | 
			
		||||
import { Space } from 'antd';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { useGetAriaLabelOfDesigner } from './hooks/useGetAriaLabelOfDesigner';
 | 
			
		||||
 | 
			
		||||
export const PageDesigner = ({ title }) => {
 | 
			
		||||
  const { designable } = useDesignable();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const { render } = useSchemaSettingsRender(
 | 
			
		||||
    fieldSchema['x-settings'] || 'PageSettings',
 | 
			
		||||
    fieldSchema['x-settings-props'],
 | 
			
		||||
  );
 | 
			
		||||
  if (!designable) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
  return (
 | 
			
		||||
    <SchemaToolbarProvider title={title}>
 | 
			
		||||
      <div className={'general-schema-designer'}>
 | 
			
		||||
        <div className={'general-schema-designer-icons'}>
 | 
			
		||||
          <Space size={2} align={'center'}>
 | 
			
		||||
            {render()}
 | 
			
		||||
          </Space>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </SchemaToolbarProvider>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const PageTabDesigner = ({ schema }) => {
 | 
			
		||||
  const { designable } = useDesignable();
 | 
			
		||||
  const { getAriaLabel } = useGetAriaLabelOfDesigner();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const { render } = useSchemaSettingsRender(
 | 
			
		||||
    fieldSchema['x-settings'] || 'PageTabSettings',
 | 
			
		||||
    fieldSchema['x-settings-props'],
 | 
			
		||||
  );
 | 
			
		||||
  if (!designable) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
  return (
 | 
			
		||||
    <SchemaToolbarProvider schema={schema}>
 | 
			
		||||
      <div className={'general-schema-designer'}>
 | 
			
		||||
        <div className={'general-schema-designer-icons'}>
 | 
			
		||||
          <Space size={3} align={'center'}>
 | 
			
		||||
            <DragHandler>
 | 
			
		||||
              <DragOutlined style={{ marginRight: 0 }} role="button" aria-label={getAriaLabel('drag-handler', 'tab')} />
 | 
			
		||||
            </DragHandler>
 | 
			
		||||
            {render()}
 | 
			
		||||
          </Space>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </SchemaToolbarProvider>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
@ -1,22 +0,0 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { render, screen, waitFor } from 'testUtils';
 | 
			
		||||
import App1 from '../demos/demo1';
 | 
			
		||||
import { GlobalThemeProvider } from '@nocobase/client';
 | 
			
		||||
 | 
			
		||||
describe('Page', () => {
 | 
			
		||||
  it('should render correctly', async () => {
 | 
			
		||||
    render(<App1 />, {
 | 
			
		||||
      wrapper: GlobalThemeProvider,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    await waitFor(() => {
 | 
			
		||||
      expect(screen.getByText(/page title/i)).toBeInTheDocument();
 | 
			
		||||
    });
 | 
			
		||||
    await waitFor(() => {
 | 
			
		||||
      expect(screen.getByText(/page content/i)).toBeInTheDocument();
 | 
			
		||||
    });
 | 
			
		||||
    await waitFor(() => {
 | 
			
		||||
      expect(document.title).toBe('Page Title - NocoBase');
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@ -1,37 +0,0 @@
 | 
			
		||||
import { ISchema } from '@formily/react';
 | 
			
		||||
import { DocumentTitleProvider, Page, SchemaComponent, SchemaComponentProvider, Application } from '@nocobase/client';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
 | 
			
		||||
const schema: ISchema = {
 | 
			
		||||
  type: 'object',
 | 
			
		||||
  properties: {
 | 
			
		||||
    page1: {
 | 
			
		||||
      type: 'void',
 | 
			
		||||
      'x-component': 'Page',
 | 
			
		||||
      title: 'Page Title',
 | 
			
		||||
      properties: {
 | 
			
		||||
        content: {
 | 
			
		||||
          type: 'void',
 | 
			
		||||
          'x-component': 'div',
 | 
			
		||||
          'x-content': 'Page Content',
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const Root = () => {
 | 
			
		||||
  return (
 | 
			
		||||
    <SchemaComponentProvider components={{ Page }}>
 | 
			
		||||
      <DocumentTitleProvider addonAfter={'NocoBase'}>
 | 
			
		||||
        <SchemaComponent schema={schema} />
 | 
			
		||||
      </DocumentTitleProvider>
 | 
			
		||||
    </SchemaComponentProvider>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const app = new Application({
 | 
			
		||||
  providers: [Root],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default app.getRootComponent();
 | 
			
		||||
@ -1,28 +0,0 @@
 | 
			
		||||
import { useFieldSchema } from '@formily/react';
 | 
			
		||||
import { useCollection_deprecated } from '@nocobase/client';
 | 
			
		||||
import { useCallback } from 'react';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * label = 'designer' + name + x-component + [x-designer] + [collectionName] + [x-collection-field] + [postfix]
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export const useGetAriaLabelOfDesigner = () => {
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const { name: _collectionName } = useCollection_deprecated();
 | 
			
		||||
  const getAriaLabel = useCallback(
 | 
			
		||||
    (name: string, postfix?: string) => {
 | 
			
		||||
      if (!fieldSchema) return '';
 | 
			
		||||
 | 
			
		||||
      const component = fieldSchema['x-component'];
 | 
			
		||||
      const designer = fieldSchema['x-designer'] ? `-${fieldSchema['x-designer']}` : '';
 | 
			
		||||
      const collectionField = fieldSchema['x-collection-field'] ? `-${fieldSchema['x-collection-field']}` : '';
 | 
			
		||||
      const collectionName = _collectionName ? `-${_collectionName}` : '';
 | 
			
		||||
      postfix = postfix ? `-${postfix}` : '';
 | 
			
		||||
 | 
			
		||||
      return `designer-${name}-${component}${designer}${collectionName}${collectionField}${postfix}`;
 | 
			
		||||
    },
 | 
			
		||||
    [fieldSchema, _collectionName],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return { getAriaLabel };
 | 
			
		||||
};
 | 
			
		||||
@ -1,25 +0,0 @@
 | 
			
		||||
import { useFieldSchema } from '@formily/react';
 | 
			
		||||
import { useCollection_deprecated } from '@nocobase/client';
 | 
			
		||||
import { useCallback } from 'react';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * label = 'schema-initializer' + x-component + [x-initializer] + [collectionName] + [postfix]
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export const useGetAriaLabelOfSchemaInitializer = () => {
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const { name } = useCollection_deprecated();
 | 
			
		||||
  const getAriaLabel = useCallback(
 | 
			
		||||
    (postfix?: string) => {
 | 
			
		||||
      if (!fieldSchema) return '';
 | 
			
		||||
      const initializer = fieldSchema['x-initializer'] ? `-${fieldSchema['x-initializer']}` : '';
 | 
			
		||||
      const collectionName = name ? `-${name}` : '';
 | 
			
		||||
      postfix = postfix ? `-${postfix}` : '';
 | 
			
		||||
 | 
			
		||||
      return `schema-initializer-${fieldSchema['x-component']}${initializer}${collectionName}${postfix}`;
 | 
			
		||||
    },
 | 
			
		||||
    [fieldSchema, name],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return { getAriaLabel };
 | 
			
		||||
};
 | 
			
		||||
@ -1,15 +0,0 @@
 | 
			
		||||
import { useActionContext } from '@nocobase/client';
 | 
			
		||||
import { useCallback } from 'react';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 判断当前区块是否在页面而不是在弹窗中
 | 
			
		||||
 */
 | 
			
		||||
export const useIsBlockInPage = () => {
 | 
			
		||||
  const { visible } = useActionContext();
 | 
			
		||||
 | 
			
		||||
  const isBlockInPage = useCallback(() => {
 | 
			
		||||
    return !visible;
 | 
			
		||||
  }, [visible]);
 | 
			
		||||
 | 
			
		||||
  return { isBlockInPage };
 | 
			
		||||
};
 | 
			
		||||
@ -1,11 +0,0 @@
 | 
			
		||||
---
 | 
			
		||||
group:
 | 
			
		||||
  title: Schema Components
 | 
			
		||||
  order: 3
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
# Page
 | 
			
		||||
 | 
			
		||||
可以与 DocumentTitleProvider 搭配使用,将 page title 显示在 document.title 上。
 | 
			
		||||
 | 
			
		||||
<code src="./demos/demo1.tsx"></code>
 | 
			
		||||
@ -1,4 +0,0 @@
 | 
			
		||||
export * from './Page';
 | 
			
		||||
export * from './FixedBlock';
 | 
			
		||||
export * from './PageTab.Settings';
 | 
			
		||||
export * from './Page.Settings';
 | 
			
		||||
@ -1,132 +0,0 @@
 | 
			
		||||
import { createStyles } from 'antd-style';
 | 
			
		||||
import { genStyleHook } from '../__builtins__';
 | 
			
		||||
 | 
			
		||||
export const useAClStyles = createStyles(({ css }) => {
 | 
			
		||||
  return css`
 | 
			
		||||
    .ant-table-cell {
 | 
			
		||||
      > .ant-space-horizontal {
 | 
			
		||||
        .ant-space-item-split:has(+ .ant-space-item:empty) {
 | 
			
		||||
          display: none;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  `;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const useStyles = genStyleHook('nb-page', (token) => {
 | 
			
		||||
  const { componentCls } = token;
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    [componentCls]: {
 | 
			
		||||
      position: 'relative',
 | 
			
		||||
      zIndex: 20,
 | 
			
		||||
      flex: 1,
 | 
			
		||||
      display: 'flex',
 | 
			
		||||
      flexDirection: 'column',
 | 
			
		||||
      '&:hover': { '> .general-schema-designer': { display: 'block' } },
 | 
			
		||||
      '.ant-page-header': { zIndex: 1, position: 'relative' },
 | 
			
		||||
      '> .general-schema-designer': {
 | 
			
		||||
        position: 'absolute',
 | 
			
		||||
        zIndex: 999,
 | 
			
		||||
        top: '0',
 | 
			
		||||
        bottom: '0',
 | 
			
		||||
        left: '0',
 | 
			
		||||
        right: '0',
 | 
			
		||||
        display: 'none',
 | 
			
		||||
        border: '0',
 | 
			
		||||
        pointerEvents: 'none',
 | 
			
		||||
        '> .general-schema-designer-icons': {
 | 
			
		||||
          zIndex: 9999,
 | 
			
		||||
          position: 'absolute',
 | 
			
		||||
          right: '2px',
 | 
			
		||||
          top: '2px',
 | 
			
		||||
          lineHeight: '16px',
 | 
			
		||||
          pointerEvents: 'all',
 | 
			
		||||
          '.ant-space-item': {
 | 
			
		||||
            backgroundColor: 'var(--colorSettings)',
 | 
			
		||||
            color: '#fff',
 | 
			
		||||
            lineHeight: '16px',
 | 
			
		||||
            width: '16px',
 | 
			
		||||
            paddingLeft: '1px',
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      '.pageHeaderCss': {
 | 
			
		||||
        backgroundColor: token.colorBgContainer,
 | 
			
		||||
        paddingInline: token.paddingLG,
 | 
			
		||||
        '&.ant-page-header-has-footer': {
 | 
			
		||||
          paddingTop: token.paddingSM,
 | 
			
		||||
          paddingBottom: '0',
 | 
			
		||||
          '.ant-page-header-heading-left': {},
 | 
			
		||||
          '.ant-page-header-footer': { marginBlockStart: '0' },
 | 
			
		||||
        },
 | 
			
		||||
        '.ant-tabs-nav': { marginBottom: '0' },
 | 
			
		||||
        '.ant-page-header-heading-title': {
 | 
			
		||||
          color: token.colorText,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      '.height0': {
 | 
			
		||||
        fontSize: 0,
 | 
			
		||||
        height: 0,
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      '.addTabBtn': {
 | 
			
		||||
        borderColor: 'var(--colorSettings)',
 | 
			
		||||
        color: 'var(--colorSettings)',
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      '.designerCss': {
 | 
			
		||||
        position: 'relative',
 | 
			
		||||
        '&:hover': { '> .general-schema-designer': { display: 'block' } },
 | 
			
		||||
        '&.nb-action-link': {
 | 
			
		||||
          '> .general-schema-designer': {
 | 
			
		||||
            top: '-10px',
 | 
			
		||||
            bottom: '-10px',
 | 
			
		||||
            left: '-10px',
 | 
			
		||||
            right: '-10px',
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        '> .general-schema-designer': {
 | 
			
		||||
          position: 'absolute',
 | 
			
		||||
          zIndex: 999,
 | 
			
		||||
          top: '0',
 | 
			
		||||
          bottom: '0',
 | 
			
		||||
          left: '0',
 | 
			
		||||
          right: '0',
 | 
			
		||||
          display: 'none',
 | 
			
		||||
          background: 'var(--colorBgSettingsHover)',
 | 
			
		||||
          border: '0',
 | 
			
		||||
          pointerEvents: 'none',
 | 
			
		||||
          '> .general-schema-designer-icons': {
 | 
			
		||||
            position: 'absolute',
 | 
			
		||||
            right: '2px',
 | 
			
		||||
            top: '2px',
 | 
			
		||||
            lineHeight: '16px',
 | 
			
		||||
            pointerEvents: 'all',
 | 
			
		||||
            '.ant-space-item': {
 | 
			
		||||
              backgroundColor: 'var(--colorSettings)',
 | 
			
		||||
              color: '#fff',
 | 
			
		||||
              lineHeight: '16px',
 | 
			
		||||
              width: '16px',
 | 
			
		||||
              paddingLeft: '1px',
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      '.pageWithFixedBlockCss': {
 | 
			
		||||
        height: '100%',
 | 
			
		||||
        '> .nb-grid:not(:last-child)': {
 | 
			
		||||
          '> .nb-schema-initializer-button': { display: 'none' },
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      '.nb-page-wrapper': {
 | 
			
		||||
        margin: token.marginLG,
 | 
			
		||||
        flex: 1,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
@ -22,7 +22,6 @@ import {
 | 
			
		||||
  SchemaSettingsDataScope,
 | 
			
		||||
  EditComponent,
 | 
			
		||||
  useFormBlockContext,
 | 
			
		||||
  removeNullCondition,
 | 
			
		||||
  useAPIClient,
 | 
			
		||||
} from '@nocobase/client';
 | 
			
		||||
import { ConfigProvider } from 'antd';
 | 
			
		||||
@ -31,7 +30,7 @@ import { Schema, SchemaOptionsContext, observer, useField, useFieldSchema } from
 | 
			
		||||
import { FormLayout } from '@formily/antd-v5';
 | 
			
		||||
import { uid } from '@formily/shared';
 | 
			
		||||
import { Field } from '@formily/core';
 | 
			
		||||
import { EditFormulaTitleField, EditTitle, EditTitleField } from '../../components/SchemaSettingOptions';
 | 
			
		||||
import { EditFormulaTitleField, EditTitle, EditTitleField } from '../../schema-settings';
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
import { SchemaSettingsRemove } from '../../components/FormFilter/SchemaSettingsRemove';
 | 
			
		||||
import { useTranslation } from '../../locale';
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,9 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { ActionInitializer } from '@nocobase/client';
 | 
			
		||||
import { Popover, Space, Button, Input } from 'antd';
 | 
			
		||||
import { useFieldSchema } from '@formily/react';
 | 
			
		||||
import { useProps } from '@nocobase/client';
 | 
			
		||||
import { ShareAltOutlined } from '@ant-design/icons';
 | 
			
		||||
 | 
			
		||||
export const OutboundLinkActionInitializer = (props) => {
 | 
			
		||||
  const schema = {
 | 
			
		||||
@ -14,3 +18,29 @@ export const OutboundLinkActionInitializer = (props) => {
 | 
			
		||||
  };
 | 
			
		||||
  return <ActionInitializer {...props} schema={schema} />;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const OutboundButton = (props) => {
 | 
			
		||||
  const { onClick } = useProps(props);
 | 
			
		||||
  const schema = useFieldSchema();
 | 
			
		||||
  const url = window.location.href.split('/', 3).join('/');
 | 
			
		||||
  return (
 | 
			
		||||
    <Popover
 | 
			
		||||
      placement="bottomRight"
 | 
			
		||||
      trigger="click"
 | 
			
		||||
      autoAdjustOverflow
 | 
			
		||||
      content={
 | 
			
		||||
        <Space.Compact style={{ width: '100%' }}>
 | 
			
		||||
          <Input defaultValue={`${url}/r/${schema['x-uid']}`} />
 | 
			
		||||
          <Button type="primary" onClick={onClick}>
 | 
			
		||||
            复制链接
 | 
			
		||||
          </Button>
 | 
			
		||||
        </Space.Compact>
 | 
			
		||||
      }
 | 
			
		||||
    >
 | 
			
		||||
      <Button>
 | 
			
		||||
        <ShareAltOutlined />
 | 
			
		||||
        外链
 | 
			
		||||
      </Button>
 | 
			
		||||
    </Popover>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,35 +1,25 @@
 | 
			
		||||
import { Field } from '@formily/core';
 | 
			
		||||
import { ISchema, useField, useFieldSchema, useForm } from '@formily/react';
 | 
			
		||||
import { ISchema, useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import {
 | 
			
		||||
  SchemaSettingsLinkageRules,
 | 
			
		||||
  SchemaSettingsModalItem,
 | 
			
		||||
  SchemaSettingsSelectItem,
 | 
			
		||||
  SchemaSettingsSwitchItem,
 | 
			
		||||
  css,
 | 
			
		||||
  mergeFilter,
 | 
			
		||||
  removeNullCondition,
 | 
			
		||||
  useBlockRequestContext,
 | 
			
		||||
  useCollection_deprecated,
 | 
			
		||||
  useCollectionManager_deprecated,
 | 
			
		||||
  useCompile,
 | 
			
		||||
  useDesignable,
 | 
			
		||||
  useFieldModeOptions,
 | 
			
		||||
  useFilterFieldProps,
 | 
			
		||||
  useFilterOptions,
 | 
			
		||||
  useFormBlockContext,
 | 
			
		||||
  useFormBlockType,
 | 
			
		||||
  useLinkageCollectionFilterOptions,
 | 
			
		||||
  useLocalVariables,
 | 
			
		||||
  useProps,
 | 
			
		||||
  useRecord,
 | 
			
		||||
  useSchemaTemplateManager,
 | 
			
		||||
  useSortFields,
 | 
			
		||||
  useVariables,
 | 
			
		||||
} from '@nocobase/client';
 | 
			
		||||
import _, { get } from 'lodash';
 | 
			
		||||
import React, { useCallback, useContext, useMemo } from 'react';
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
import React, { useCallback, useMemo } from 'react';
 | 
			
		||||
import { useTranslation } from '../locale';
 | 
			
		||||
import { FormFilterScope } from './FormFilter/FormFilterScope';
 | 
			
		||||
import { FormFilterScope } from '../components/FormFilter/FormFilterScope';
 | 
			
		||||
 | 
			
		||||
export const useFormulaTitleOptions = () => {
 | 
			
		||||
  const compile = useCompile();
 | 
			
		||||
@ -376,3 +366,132 @@ export const SetFilterScope = (props) => {
 | 
			
		||||
  );
 | 
			
		||||
  return <SchemaSettingsModalItem title={t('Custom filter')} width={770} schema={schema} onSubmit={onSubmit} />;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function AfterSuccess() {
 | 
			
		||||
  const { dn } = useDesignable();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const component = fieldSchema.parent.parent['x-component'];
 | 
			
		||||
  const schema = {
 | 
			
		||||
    type: 'object',
 | 
			
		||||
    title: t('After successful submission'),
 | 
			
		||||
    properties: {
 | 
			
		||||
      successMessage: {
 | 
			
		||||
        title: t('Popup message'),
 | 
			
		||||
        'x-decorator': 'FormItem',
 | 
			
		||||
        'x-component': 'Input.TextArea',
 | 
			
		||||
        'x-component-props': {},
 | 
			
		||||
      },
 | 
			
		||||
      dataClear: {
 | 
			
		||||
        title: t('Clear data method'),
 | 
			
		||||
        enum: [
 | 
			
		||||
          { label: t('Automatic clear'), value: false },
 | 
			
		||||
          { label: t('Manually clear'), value: true },
 | 
			
		||||
        ],
 | 
			
		||||
        'x-decorator': 'FormItem',
 | 
			
		||||
        'x-component': 'Radio.Group',
 | 
			
		||||
        'x-component-props': {},
 | 
			
		||||
      },
 | 
			
		||||
      manualClose: {
 | 
			
		||||
        title: t('Popup close method'),
 | 
			
		||||
        enum: [
 | 
			
		||||
          { label: t('Automatic close'), value: false },
 | 
			
		||||
          { label: t('Manually close'), value: true },
 | 
			
		||||
        ],
 | 
			
		||||
        'x-decorator': 'FormItem',
 | 
			
		||||
        'x-component': 'Radio.Group',
 | 
			
		||||
        'x-component-props': {},
 | 
			
		||||
      },
 | 
			
		||||
      redirecting: {
 | 
			
		||||
        title: t('Then'),
 | 
			
		||||
        enum: [
 | 
			
		||||
          { label: t('Stay on current page'), value: false },
 | 
			
		||||
          { label: t('Redirect to'), value: true },
 | 
			
		||||
        ],
 | 
			
		||||
        'x-decorator': 'FormItem',
 | 
			
		||||
        'x-component': 'Radio.Group',
 | 
			
		||||
        'x-component-props': {},
 | 
			
		||||
        'x-reactions': {
 | 
			
		||||
          target: 'redirectTo',
 | 
			
		||||
          fulfill: {
 | 
			
		||||
            state: {
 | 
			
		||||
              visible: '{{!!$self.value}}',
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      redirectTo: {
 | 
			
		||||
        title: t('Link'),
 | 
			
		||||
        'x-decorator': 'FormItem',
 | 
			
		||||
        'x-component': 'Input',
 | 
			
		||||
        'x-component-props': {},
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
  if (!(component as string).includes('Form')) {
 | 
			
		||||
    delete schema.properties.dataClear;
 | 
			
		||||
  }
 | 
			
		||||
  return (
 | 
			
		||||
    <SchemaSettingsModalItem
 | 
			
		||||
      title={t('After successful submission')}
 | 
			
		||||
      initialValues={fieldSchema?.['x-action-settings']?.['onSuccess']}
 | 
			
		||||
      schema={{ ...schema } as ISchema}
 | 
			
		||||
      onSubmit={(onSuccess) => {
 | 
			
		||||
        fieldSchema['x-action-settings']['onSuccess'] = onSuccess;
 | 
			
		||||
        dn.emit('patch', {
 | 
			
		||||
          schema: {
 | 
			
		||||
            ['x-uid']: fieldSchema['x-uid'],
 | 
			
		||||
            'x-action-settings': fieldSchema['x-action-settings'],
 | 
			
		||||
          },
 | 
			
		||||
        });
 | 
			
		||||
      }}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 添加跳转页面选项
 | 
			
		||||
export const SessionSubmit = () => {
 | 
			
		||||
  const { dn } = useDesignable();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  return (
 | 
			
		||||
    <SchemaSettingsSwitchItem
 | 
			
		||||
      title={t('Navigate to new page')}
 | 
			
		||||
      checked={!!fieldSchema?.['x-action-settings']?.sessionSubmit}
 | 
			
		||||
      onChange={(value) => {
 | 
			
		||||
        fieldSchema['x-action-settings'].sessionSubmit = value;
 | 
			
		||||
        dn.emit('patch', {
 | 
			
		||||
          schema: {
 | 
			
		||||
            ['x-uid']: fieldSchema['x-uid'],
 | 
			
		||||
            'x-action-settings': {
 | 
			
		||||
              ...fieldSchema['x-action-settings'],
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        });
 | 
			
		||||
      }}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function SessionUpdate() {
 | 
			
		||||
  const { dn } = useDesignable();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  return (
 | 
			
		||||
    <SchemaSettingsSwitchItem
 | 
			
		||||
      title={t('更新询问')}
 | 
			
		||||
      checked={!!fieldSchema?.['x-action-settings']?.sessionUpdate}
 | 
			
		||||
      onChange={(value) => {
 | 
			
		||||
        fieldSchema['x-action-settings'].sessionUpdate = value;
 | 
			
		||||
        dn.emit('patch', {
 | 
			
		||||
          schema: {
 | 
			
		||||
            ['x-uid']: fieldSchema['x-uid'],
 | 
			
		||||
            'x-action-settings': {
 | 
			
		||||
              ...fieldSchema['x-action-settings'],
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        });
 | 
			
		||||
      }}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@ -29,7 +29,7 @@ export const LinkManager = () => {
 | 
			
		||||
    updateLink();
 | 
			
		||||
    // 更新表格数据
 | 
			
		||||
    const mergedArray = tableData.map((item) => {
 | 
			
		||||
      const matchingItem = item.id == formData.id;
 | 
			
		||||
      const matchingItem = item.id === formData.id;
 | 
			
		||||
      if (matchingItem) {
 | 
			
		||||
        return formData;
 | 
			
		||||
      }
 | 
			
		||||
@ -0,0 +1,3 @@
 | 
			
		||||
export * from './HomePageConfiguration';
 | 
			
		||||
export * from './LinkManager';
 | 
			
		||||
export * from './TokenConfiguration';
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user