// Copyright 2021 99cloud
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React, { Component } from 'react';
import { inject, observer } from 'mobx-react';
import { Modal, Button, Tooltip } from 'antd';
import { isArray, isFunction, isBoolean } from 'lodash';
import Confirm from 'components/Confirm';
import PropTypes from 'prop-types';
import Notify from 'components/Notify';
import classnames from 'classnames';
import { firstUpperCase, allSettled } from 'utils';
import styles from './index.less';
function getDefaultMsg(action, data) {
const { actionName, title } = action;
const name = isArray(data) ? data.map((it) => it.name).join(', ') : data.name;
const submitErrorMsg = t('Unable to {action} {name}.', {
action: actionName.toLowerCase() || title,
name,
});
const performErrorMsg = t('You are not allowed to { action } {name}.', {
action: actionName.toLowerCase() || title,
name,
});
const submitSuccessMsg = firstUpperCase(
t('{action} {name} successfully.', {
action: actionName.toLowerCase() || title,
name,
})
);
const confirmContext = t('Are you sure to { action } {name}?', {
action: actionName.toLowerCase() || title,
name,
});
return {
submitErrorMsg,
submitSuccessMsg,
confirmContext,
performErrorMsg,
};
}
@inject('rootStore')
@observer
class ActionButton extends Component {
static propTypes() {
return {
title: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
perform: PropTypes.func.isRequired,
item: PropTypes.object,
actionType: PropTypes.string,
icon: PropTypes.string,
isAllowed: PropTypes.bool,
needHide: PropTypes.bool,
buttonType: PropTypes.string,
items: PropTypes.array,
isBatch: PropTypes.bool,
path: PropTypes.string,
onFinishAction: PropTypes.func,
action: PropTypes.any,
containerProps: PropTypes.any,
maxLength: PropTypes.number,
isFirstAction: PropTypes.bool,
onClickAction: PropTypes.func,
visible: PropTypes.bool,
};
}
static defaultProps = {
item: undefined,
isAllowed: false,
confirm: false,
needHide: true,
buttonType: 'link',
isLink: false,
items: [],
isBatch: false,
path: '',
containerProps: {},
maxLength: 0,
isFirstAction: false,
onClickAction: null,
visible: false,
};
constructor(props) {
super(props);
const { id } = props;
if (!id) {
throw Error('need id!');
}
this.state = {
visible: false,
submitLoading: false,
};
}
get routing() {
return this.props.rootStore.routing;
}
onClick = () => {
const { actionType, onClickAction } = this.props;
switch (actionType) {
case 'confirm':
this.onShowConfirm();
break;
case 'link': {
const { action, item, containerProps } = this.props;
const { path } = action;
if (isFunction(path)) {
const newPath = path(item, containerProps);
this.routing.push(newPath);
} else {
this.routing.push(path);
}
break;
}
default:
this.formRef = React.createRef();
this.showModalAction();
}
if (onClickAction) {
onClickAction();
}
};
handleSubmitLoading = (flag) => {
this.setState({
submitLoading: !!flag,
});
};
handleSubmit = (values) => {
const { item, isBatch, items } = this.props;
if (!this.onSubmit) return;
this.handleSubmitLoading(true);
const data = isBatch ? items : item;
const result = this.onSubmit(values, data);
if (result instanceof Promise) {
result
.then(
() => {
this.onShowSuccess(data);
},
(error) => {
this.onShowError(data, error);
}
)
.finally(() => {
this.handleSubmitLoading();
});
} else {
this.handleSubmitLoading();
if (result) {
this.onShowSuccess(data);
} else {
this.onShowError(data, result);
}
}
};
onOK = () => {
const { onSubmit, form, item, isBatch, items } = this.props;
if (!onSubmit) return;
this.handleSubmitLoading(true);
form.validateFields([], (err, values) => {
if (err) {
// eslint-disable-next-line no-console
console.log('Values has error: ', err);
return;
}
// eslint-disable-next-line no-console
console.log('Received values of form: ', values);
const data = isBatch ? items : item;
const result = onSubmit(form.getFieldsValue(), data);
if (result instanceof Promise) {
result
.then(
() => {
this.onShowSuccess(data);
},
(error) => {
this.onShowError(data, error);
}
)
.finally(() => {
this.handleSubmitLoading();
});
} else {
this.handleSubmitLoading();
if (result) {
this.onShowSuccess(data);
} else {
this.onShowError(data, result);
}
}
});
};
onShowSuccess = (data, afterSubmit) => {
const { submitSuccessMsg } = this.props.action;
const message = submitSuccessMsg
? submitSuccessMsg(data)
: getDefaultMsg(this.props.action, data).submitSuccessMsg;
Notify.success(message);
this.onCallback(true, false, afterSubmit);
};
// eslint-disable-next-line no-shadow
onCallback = (success, fail, afterSubmit) => {
const { onFinishAction, id } = this.props;
if (onFinishAction) {
const isDelete = id === 'delete';
setTimeout(() => {
onFinishAction(success, fail, isDelete, afterSubmit);
}, 500);
}
};
// eslint-disable-next-line no-unused-vars
onShowError = (data, error) => {
// this.handleModalVisible();
const { showConfirmErrorBeforeSubmit, confirmErrorMessageBeforeSubmit } =
this.props.action;
if (showConfirmErrorBeforeSubmit) {
Confirm.error({
content: confirmErrorMessageBeforeSubmit,
});
this.onCallback(false, true);
return;
}
const { submitErrorMsg } = this.props.action;
const { data: responseData } = (error || {}).response || error || {};
const realError = responseData || error;
const message = submitErrorMsg
? submitErrorMsg(data, realError)
: getDefaultMsg(this.props.action, data).submitErrorMsg;
Notify.errorWithDetail(realError, message);
this.onCallback(false, true);
};
onShowConfirm = async () => {
const {
perform,
title,
confirmContext,
okText,
cancelText,
onSubmit,
afterSubmit,
} = this.props.action;
const { item, items, isBatch, containerProps, onCancelAction } = this.props;
const data = isBatch ? items : item;
const content = confirmContext
? confirmContext(data)
: getDefaultMsg(this.props.action, data).confirmContext;
try {
perform(data).then(
() => {
Confirm.confirm({
title,
content,
okText,
cancelText,
onOk: () =>
this.onConfirmOK(
data,
onSubmit,
isBatch,
containerProps,
afterSubmit
),
onCancel: () => {
onCancelAction && onCancelAction();
},
});
},
(error) => {
const message =
error || getDefaultMsg(this.props.action, data).performErrorMsg;
Confirm.error({
content: message,
});
}
);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
const message =
error || getDefaultMsg(this.props.action, data).performErrorMsg;
Confirm.error({
content: message,
});
}
};
onSubmitOne = (data, onSubmit, containerProps, afterSubmit) =>
new Promise((resolve, reject) => {
const result = onSubmit(data, containerProps);
if (result instanceof Promise) {
result.then(
() => {
this.onShowSuccess(data, afterSubmit);
resolve();
},
(error) => {
reject(error);
}
);
} else if (result) {
this.onShowSuccess(data, afterSubmit);
resolve();
} else {
reject(result);
}
}).catch((error) => {
this.onShowError(data, error);
});
onSubmitBatch = (data, onSubmit, containerProps, isBatch, afterSubmit) =>
new Promise((resolve, reject) => {
const promises = data.map((it, index) =>
onSubmit(it, containerProps, isBatch, index, data)
);
const results = allSettled(promises);
results.then((res) => {
const failedDatas = res
.map((it, idx) => {
if (it.status === 'rejected') {
return {
data: data[idx],
reason: it.reason,
};
}
return null;
})
.filter((it) => !!it);
if (failedDatas.length === 0) {
this.onShowSuccess(data, afterSubmit);
return resolve();
}
failedDatas.forEach((it) => {
this.onShowError(it.data, it.reason);
});
if (failedDatas.length === data.length) {
return reject();
}
return resolve();
});
});
onConfirmOK = (data, onSubmit, isBatch, containerProps, afterSubmit) => {
if (isBatch) {
return this.onSubmitBatch(
data,
onSubmit,
containerProps,
isBatch,
afterSubmit
);
}
return this.onSubmitOne(data, onSubmit, containerProps, afterSubmit);
};
onClickModalActionOk = () => {
const { containerProps } = this.props;
return this.formRef.current.wrappedInstance.onClickSubmit(
(success, fail) => {
this.handleSubmitLoading();
this.onClickModalActionCancel(true);
this.onCallback(success, fail);
},
() => {
this.handleSubmitLoading(true);
},
containerProps
);
};
onClickModalActionCancel = (finish) => {
if (!isBoolean(finish)) {
this.formRef.current.wrappedInstance.onClickCancel();
}
const { onCancelAction } = this.props;
this.setState(
{
visible: false,
},
() => {
onCancelAction && onCancelAction();
}
);
};
getModalWidth = (size) => {
switch (size) {
case 'small':
return 520;
case 'middle':
return 720;
case 'large':
return 1200;
default:
return 520;
}
};
showModalAction() {
this.setState({
visible: true,
});
}
renderModal() {
const { visible, submitLoading } = this.state;
if (!visible) {
return null;
}
const { title, action, item, containerProps, items } = this.props;
const ActionComponent = action;
const { modalSize, okText, cancelText, id, className, readOnly } = action;
const width = this.getModalWidth(modalSize);
const modalProps = {
title,
visible,
className: classnames(`modal-${id}`, styles['modal-action'], className),
width,
onOk: () => this.onClickModalActionOk(),
onCancel: this.onClickModalActionCancel,
confirmLoading: submitLoading,
okText,
cancelText,
};
if (readOnly) {
modalProps.cancelButtonProps = {
style: { display: 'none' },
};
}
return (
);
}
render() {
const {
isAllowed,
needHide,
buttonType,
buttonClassName,
name,
id,
title,
danger,
style,
maxLength,
isFirstAction,
} = this.props;
if (!isAllowed && needHide) {
return null;
}
const buttonText = name || title;
let showTip = false;
if (isFirstAction && buttonText && buttonText.length > maxLength) {
showTip = true;
}
const button = (
);
const buttonRender = showTip ? (
{button}
) : (
button
);
return (
<>
{buttonRender}
{this.renderModal()}
>
);
}
}
export default ActionButton;