mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-30 02:48:55 +08:00
241 lines
6.6 KiB
TypeScript
241 lines
6.6 KiB
TypeScript
import React from 'react';
|
|
import isPlainObject from 'lodash/isPlainObject';
|
|
import {RendererEnv} from './env';
|
|
import {RendererProps} from './factory';
|
|
import {LocaleContext, TranslateFn} from './locale';
|
|
import {RootRenderer} from './RootRenderer';
|
|
import {SchemaRenderer} from './SchemaRenderer';
|
|
import Scoped from './Scoped';
|
|
import {IRendererStore} from './store';
|
|
import {ThemeContext} from './theme';
|
|
import {Schema, SchemaNode} from './types';
|
|
import {autobind, isEmpty} from './utils/helper';
|
|
import {RootStoreContext} from './WithRootStore';
|
|
import {StatusScoped, StatusScopedProps} from './StatusScoped';
|
|
|
|
export interface RootRenderProps {
|
|
location?: Location;
|
|
theme?: string;
|
|
data?: Record<string, any>;
|
|
locale?: string;
|
|
[propName: string]: any;
|
|
}
|
|
|
|
export interface RootProps extends StatusScopedProps {
|
|
schema: SchemaNode;
|
|
rootStore: IRendererStore;
|
|
env: RendererEnv;
|
|
theme: string;
|
|
pathPrefix?: string;
|
|
locale?: string;
|
|
translate?: TranslateFn;
|
|
[propName: string]: any;
|
|
}
|
|
|
|
export interface RootWrapperProps {
|
|
env: RendererEnv;
|
|
children: React.ReactNode | Array<React.ReactNode>;
|
|
schema: SchemaNode;
|
|
rootStore: IRendererStore;
|
|
theme: string;
|
|
data?: Record<string, any>;
|
|
context?: Record<string, any>;
|
|
[propName: string]: any;
|
|
}
|
|
|
|
const rootWrappers: Array<(props: RootWrapperProps) => React.ReactNode> = [];
|
|
|
|
export function addRootWrapper(
|
|
fn: (props: RootWrapperProps) => React.ReactNode
|
|
) {
|
|
rootWrappers.push(fn);
|
|
}
|
|
|
|
export class Root extends React.Component<RootProps> {
|
|
@autobind
|
|
resolveDefinitions(name: string) {
|
|
const definitions = (this.props.schema as Schema).definitions;
|
|
if (!name || isEmpty(definitions)) {
|
|
return {};
|
|
}
|
|
return definitions && definitions[name];
|
|
}
|
|
|
|
render() {
|
|
const {
|
|
schema,
|
|
rootStore,
|
|
env,
|
|
pathPrefix,
|
|
location,
|
|
data,
|
|
context,
|
|
locale,
|
|
translate,
|
|
...rest
|
|
} = this.props;
|
|
const theme = env.theme;
|
|
let themeName = this.props.theme || 'cxd';
|
|
|
|
if (themeName === 'default') {
|
|
themeName = 'cxd';
|
|
}
|
|
|
|
return (
|
|
<RootStoreContext.Provider value={rootStore}>
|
|
<ThemeContext.Provider value={themeName}>
|
|
<LocaleContext.Provider value={this.props.locale!}>
|
|
{
|
|
rootWrappers.reduce(
|
|
(props: RootWrapperProps, wrapper) => {
|
|
return {
|
|
...props,
|
|
children: wrapper(props)
|
|
};
|
|
},
|
|
{
|
|
pathPrefix: pathPrefix || '',
|
|
schema: isPlainObject(schema)
|
|
? {
|
|
type: 'page',
|
|
...(schema as any)
|
|
}
|
|
: schema,
|
|
...rest,
|
|
render: renderChild,
|
|
rootStore: rootStore,
|
|
resolveDefinitions: this.resolveDefinitions,
|
|
location: location,
|
|
data,
|
|
env: env,
|
|
classnames: theme.classnames,
|
|
classPrefix: theme.classPrefix,
|
|
locale: locale,
|
|
translate: translate,
|
|
children: (
|
|
<RootRenderer
|
|
pathPrefix={pathPrefix || ''}
|
|
schema={
|
|
isPlainObject(schema)
|
|
? {
|
|
type: 'page',
|
|
...(schema as any)
|
|
}
|
|
: schema
|
|
}
|
|
{...rest}
|
|
render={renderChild}
|
|
rootStore={rootStore}
|
|
resolveDefinitions={this.resolveDefinitions}
|
|
location={location}
|
|
data={data}
|
|
context={context}
|
|
env={env}
|
|
classnames={theme.classnames}
|
|
classPrefix={theme.classPrefix}
|
|
locale={locale}
|
|
translate={translate}
|
|
/>
|
|
)
|
|
} as RootWrapperProps
|
|
).children
|
|
}
|
|
</LocaleContext.Provider>
|
|
</ThemeContext.Provider>
|
|
</RootStoreContext.Provider>
|
|
);
|
|
}
|
|
}
|
|
|
|
export interface renderChildProps
|
|
extends Partial<Omit<RendererProps, 'statusStore'>>,
|
|
StatusScopedProps {
|
|
env: RendererEnv;
|
|
}
|
|
export type ReactElement = React.ReactNode[] | JSX.Element | null | false;
|
|
|
|
const StatusScopedSchemaRenderer = StatusScoped(SchemaRenderer);
|
|
|
|
export function renderChildren(
|
|
prefix: string,
|
|
node: SchemaNode,
|
|
props: renderChildProps
|
|
): ReactElement {
|
|
if (Array.isArray(node)) {
|
|
var elemKey = props.key || props.propKey || props.id || '';
|
|
|
|
return node.map((node, index) =>
|
|
renderChild(`${prefix}/${index}`, node, {
|
|
...props,
|
|
key: `${elemKey ? `${elemKey}-` : ''}${index}`
|
|
})
|
|
);
|
|
}
|
|
|
|
return renderChild(prefix, node, props);
|
|
}
|
|
|
|
export function renderChild(
|
|
prefix: string,
|
|
node: SchemaNode,
|
|
props: renderChildProps
|
|
): ReactElement {
|
|
if (Array.isArray(node)) {
|
|
return renderChildren(prefix, node, props);
|
|
}
|
|
|
|
const typeofnode = typeof node;
|
|
|
|
if (typeofnode === 'undefined' || node === null) {
|
|
return null;
|
|
} else if (React.isValidElement(node)) {
|
|
return node;
|
|
}
|
|
|
|
let schema: Schema =
|
|
typeofnode === 'string' || typeofnode === 'number'
|
|
? {type: 'tpl', tpl: String(node)}
|
|
: (node as Schema);
|
|
|
|
const transform = props.propsTransform;
|
|
|
|
if (transform) {
|
|
props = {...props};
|
|
delete props.propsTransform;
|
|
|
|
props = transform(props);
|
|
}
|
|
|
|
if (
|
|
['dialog', 'drawer'].includes(schema?.type) &&
|
|
!schema?.component &&
|
|
!schema?.children
|
|
) {
|
|
// 因为状态判断实在 SchemaRenderer 里面判断的
|
|
// 找渲染器也是在那,所以没办法在之前根据渲染器信息来包裹个组件下发 statusStore
|
|
// 所以这里先根据 type 来处理一下
|
|
// 等后续把状态处理再抽一层,可以把此处放到 SchemaRenderer 里面去
|
|
return (
|
|
<StatusScopedSchemaRenderer
|
|
render={renderChild as any}
|
|
{...props}
|
|
schema={schema}
|
|
propKey={schema.key}
|
|
$path={`${prefix ? `${prefix}/` : ''}${(schema && schema.type) || ''}`}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<SchemaRenderer
|
|
render={renderChild as any}
|
|
{...props}
|
|
schema={schema}
|
|
propKey={schema.key}
|
|
$path={`${prefix ? `${prefix}/` : ''}${(schema && schema.type) || ''}`}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export default StatusScoped(Scoped(Root));
|