canner
Version:
Build CMS in few lines of code for different data sources
236 lines (220 loc) • 7.41 kB
Flow
// @flow
import * as React from 'react';
import {Button, Icon, Spin, Modal} from 'antd';
import styled from 'styled-components';
import {injectIntl} from 'react-intl';
import type {HOCProps} from './types';
import type Intl from 'react-intl';
const confirm = Modal.confirm;
export const RENDER_CHILDREN = 0;
export const RENDER_COMPONENT = 1;
export const RENDER_NULL = 2;
const ButtonWrapper = styled.div`
text-align: right;
`
type State = {
loading: boolean,
loadingTip: string
}
type Props = HOCProps & {
intl: Intl
}
export default function withRoute(Com: React.ComponentType<*>) {
return class ComWithRoute extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
const {intl} = props;
this.state = {
loading: false,
loadingTip: intl.formatMessage({id: 'hocs.route.loadingTip'})
}
}
deploy = () => {
const {refId, deploy, intl} = this.props;
this.setState({
loading: true,
loadingTip: intl.formatMessage({id: 'hocs.route.deployingTip'}),
});
deploy(refId.getPathArr()[0])
.then(this.success)
.catch(this.fail);
}
reset = () => {
const {refId, reset, intl} = this.props;
this.setState({
loading: true,
loadingTip: intl.formatMessage({id: 'hocs.route.resetingTip'}),
});
reset(refId.getPathArr()[0])
.then(this.success)
.catch(this.fail);
}
success = () => {
const {goTo, routes, hideBackButton} = this.props;
setTimeout(() => {
this.setState({
loading: false
}, () => {
!hideBackButton && goTo({pathname: routes[0]});
});
}, 400);
}
fail = () => {
this.setState({
loading: false
});
}
discard = () => {
const {goTo, routes, routerParams, reset, refId, dataChanged, intl, hideBackButton} = this.props;
const resetCondFn = () => {
if (routerParams.operator === 'create') {
reset(refId.getPathArr()[0]).then(() => !hideBackButton && goTo({pathname: routes.join('/')}));
} else {
reset(refId.getPathArr()[0]).then(() => !hideBackButton && goTo({pathname: routes.slice(0, -1).join('/')}));
}
}
if (dataChanged && Object.keys(dataChanged).length > 0) {
confirm({
title: intl.formatMessage({id: 'hocs.route.confirm.title'}),
content: intl.formatMessage({id: 'hocs.route.confirm.content'}),
okText: intl.formatMessage({id: 'hocs.route.confirm.okText'}),
cancelText: intl.formatMessage({id: 'hocs.route.confirm.cancelText'}),
onOk: () => {
return new Promise(resolve => {
setTimeout(resolve, 200);
}).then()
.then(() => {
resetCondFn();
});
},
onCancel: () => {
},
});
} else {
resetCondFn();
}
}
render() {
const {loading, loadingTip} = this.state;
let {routes, pattern, path, intl, routerParams, refId, renderChildren, hideButtons, hideBackButton, uiParams} = this.props;
const renderType = getRenderType({
pattern,
routes,
path,
routerParams
});
const pathArrLength = refId.getPathArr().length;
const routesLength = routes.length;
const {operator} = routerParams;
let renderKeys = uiParams && uiParams.updateKeys; // render all
if (operator === 'create') {
renderKeys = uiParams && uiParams.createKeys;
}
return <Spin tip={loadingTip} spinning={loading}>
{
// quick fix for route array's children
// need to find a stable way to control route
(renderType === RENDER_CHILDREN && !hideBackButton && pattern === 'array' && (routesLength === pathArrLength || (routesLength + 1 === pathArrLength && operator === 'create'))) &&
<Button onClick={this.discard} style={{marginBottom: 16}} data-testid="back-button">
<Icon type="arrow-left" /> {intl.formatMessage({id: 'hocs.route.backText'})}
</Button>
}
{
renderType === RENDER_CHILDREN && renderChildren(node => {
return {
hidden: renderKeys && renderKeys.indexOf(node.keyName) === -1,
refId
};
})
}
{
// quick fix for route array's children
// need to find a stable way to control route
(renderType === RENDER_CHILDREN && pattern === 'array' && !hideButtons && (routesLength === pathArrLength || (routesLength + 1 === pathArrLength && operator === 'create'))) &&
<ButtonWrapper>
<Button style={{marginRight: 16}} type="primary" onClick={this.deploy} data-testid="confirm-button">
{intl.formatMessage({id: 'hocs.route.confirmText'})}
</Button>
<Button onClick={this.reset} data-testid="reset-button">
{intl.formatMessage({id: 'hocs.route.resetText'})}
</Button>
</ButtonWrapper>
}
{
renderType === RENDER_COMPONENT && <Com {...this.props} />
}
</Spin>
}
}
}
export function getRenderType({
routes,
path,
pattern,
routerParams
}: {
routes: Array<string>,
path: string,
pattern: string,
routerParams: Object
}) {
const paths = genPaths(path, pattern);
const pathsLength = paths.length;
const routesLength = routes.length;
if (routes[0] !== paths[0]) {
return RENDER_NULL;
}
const type = pattern.split('.').slice(-1)[0];
const {operator} = routerParams;
if (type === 'object') {
if (routesLength === pathsLength && isCompleteContain(paths, routes)) {
return RENDER_COMPONENT;
}
if (routesLength < pathsLength) {
return RENDER_COMPONENT;
}
if (routesLength > pathsLength) {
return RENDER_CHILDREN;
}
return RENDER_NULL;
} else if (type === 'array') {
if (routesLength === pathsLength && isCompleteContain(paths, routes)) {
return RENDER_CHILDREN;
}
if (routesLength < pathsLength) {
if (routesLength === pathsLength - 1 && operator === 'create') {
return RENDER_CHILDREN;
}
return RENDER_COMPONENT;
}
if (routesLength > pathsLength) {
return RENDER_COMPONENT;
}
return RENDER_NULL;
} else {
if (routesLength === pathsLength && isCompleteContain(paths, routes)) {
return RENDER_NULL;
}
if (routesLength < pathsLength) {
return RENDER_COMPONENT;
}
if (routesLength > pathsLength) {
return RENDER_COMPONENT;
}
return RENDER_NULL;
}
}
export function isCompleteContain(paths: Array<string>, routes: Array<string>) {
return paths.map((key, i) => routes[i] === key || key === '__ARRAY_INDEX__')
.reduce((result: any, curr: any) => result && curr);
}
export function genPaths(path: string, pattern: string): Array<string> {
const patterns = pattern.split('.');
const indexs = patterns.map((type, i) => type === 'array' ? i : -1)
.filter(index => index !== -1);
let paths = path.split('/')
.map((route, i) => indexs.indexOf(i) === -1 ? route : [].concat(route, '__ARRAY_INDEX__'))
.reduce((result: any, curr: any) => result.concat(curr), []);
return paths;
}