refactor: remove error-fallback and __builtins__ from plugin-core
This commit is contained in:
parent
dbd5622f27
commit
8d580b72b2
@ -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,45 +0,0 @@
|
|||||||
import { Button, Result, Typography } from 'antd';
|
|
||||||
import React, { FC } from 'react';
|
|
||||||
import { FallbackProps, useErrorBoundary } from 'react-error-boundary';
|
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
const { Paragraph, Text, Link } = Typography;
|
|
||||||
|
|
||||||
export const ErrorFallback: FC<FallbackProps> = ({ error }) => {
|
|
||||||
const { resetBoundary } = useErrorBoundary();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
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">
|
|
||||||
here
|
|
||||||
</Link>
|
|
||||||
</Trans>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{ backgroundColor: 'white' }}>
|
|
||||||
<Result
|
|
||||||
style={{ maxWidth: '60vw', margin: 'auto' }}
|
|
||||||
status="error"
|
|
||||||
title={t('Render Failed')}
|
|
||||||
subTitle={subTitle}
|
|
||||||
extra={[
|
|
||||||
<Button type="primary" key="feedback" href="https://github.com/nocobase/nocobase/issues" target="_blank">
|
|
||||||
{t('Feedback')}
|
|
||||||
</Button>,
|
|
||||||
<Button key="try" onClick={resetBoundary}>
|
|
||||||
{t('Try again')}
|
|
||||||
</Button>,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Paragraph copyable>
|
|
||||||
<Text type="danger" style={{ whiteSpace: 'pre-line', textAlign: 'center' }}>
|
|
||||||
{error.stack}
|
|
||||||
</Text>
|
|
||||||
</Paragraph>
|
|
||||||
</Result>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -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';
|
|
Loading…
Reference in New Issue
Block a user