Merge pull request #231 from catchonme/master

tree支持添加/编辑/删除
This commit is contained in:
liaoxuezhi 2019-09-11 10:10:30 +08:00 committed by GitHub
commit 334e80cddc
8 changed files with 530 additions and 54 deletions

View File

@ -1,3 +1,33 @@
@mixin tree-input {
> svg {
display: inline-block;
cursor: pointer;
position: relative;
top: 2px;
width: px2rem(16px);
height: px2rem(16px);
margin-left: px2rem(5px);
}
> input {
margin-left: px2rem(15px);
padding: px2rem(5px);
width: px2rem(150px);
height: px2rem(25px);
color: $Form-input-color;
&::placeholder {
color: $Form-input-placeholderColor;
user-select: none;
}
&:focus {
outline: none;
border: $borderWidth solid $info;
}
}
}
// todo
.#{$ns}TreeControl {
border: 1px solid $Form-input-borderColor;
@ -29,27 +59,79 @@
&.is-folded {
display: none;
}
> li {
@include tree-input;
}
}
&-item {
line-height: px2rem(30px);
position: relative;
.#{$ns}Tree-item-icons {
visibility: hidden;
transition: visibility .1s ease;
}
> a {
color: inherit;
&:hover {
text-decoration: none;
> span.#{$ns}Tree-item-icons {
visibility: visible;
}
}
> span > svg {
display: inline-block;
cursor: pointer;
position: relative;
top: 2px;
width: px2rem(16px);
height: px2rem(16px);
margin-left: px2rem(5px);
}
}
&--isLeaf > a {
padding-left: $Tree-itemArrowWidth + $gap-xs;
}
&--isEdit {
display: inline-block;
@include tree-input;
}
}
&-rootItem > a > i {
margin-left: 0 !important;
&-rootItem {
> a > i {
margin-left: 0 !important;
}
.#{$ns}Tree-addTop {
height: px2rem(25px);
line-height: px2rem(25px);
cursor: pointer;
padding-left: $Tree-indent;
> p {
> svg {
position: relative;
top: 2px;
width: px2rem(16px);
height: px2rem(16px);
}
> span {
padding-left: px2rem(5px);
}
}
&-input {
@include tree-input
}
}
}
&-itemArrow {

View File

@ -5,16 +5,17 @@
*/
import React from 'react';
import {eachTree, isVisible} from '../utils/helper';
import {eachTree, isVisible, isObject, autobind} from '../utils/helper';
import {Option, Options, value2array} from './Checkboxes';
import {ClassNamesFn, themeable} from '../theme';
import {highlight} from '../renderers/Form/Options';
import {Icon} from './icons';
interface TreeSelectorProps {
classPrefix: string;
classnames: ClassNamesFn;
highlightTxt: string;
highlightTxt?: string;
showIcon?: boolean;
// 是否默认都展开
@ -53,11 +54,26 @@ interface TreeSelectorProps {
selfDisabledAffectChildren?: boolean;
minLength?: number;
maxLength?: number;
addMode?: 'dialog' | 'normal';
addable?: boolean;
onAdd?: Function;
openAddDialog?: Function;
editMode?: 'dialog' | 'normal';
onEdit?: Function;
editable?: boolean;
openEditDialog?: Function;
deletable?: boolean;
onRemove?: Function;
}
interface TreeSelectorState {
value: Array<any>;
unfolded: {[propName: string]: string};
editItem: Option | null;
addItem: Option | null;
addingItem: Option | null;
editingItem: Option | null;
addTop: boolean;
}
export class TreeSelector extends React.Component<TreeSelectorProps, TreeSelectorState> {
@ -87,12 +103,6 @@ export class TreeSelector extends React.Component<TreeSelectorProps, TreeSelecto
};
componentWillMount() {
this.renderList = this.renderList.bind(this);
this.handleSelect = this.handleSelect.bind(this);
this.clearSelect = this.clearSelect.bind(this);
this.handleCheck = this.handleCheck.bind(this);
this.toggleUnfolded = this.toggleUnfolded.bind(this);
const props = this.props;
this.setState({
@ -104,7 +114,12 @@ export class TreeSelector extends React.Component<TreeSelectorProps, TreeSelecto
valueField: props.valueField,
options: props.data
}),
unfolded: this.syncUnFolded(props)
unfolded: this.syncUnFolded(props),
editItem: null, // 点击编辑时的 item
addItem: null, // 点击添加时的 item
addingItem: null, // 添加后的 item
editingItem: null, // 编辑后的 item
addTop: false // 添加一级
});
}
@ -155,8 +170,10 @@ export class TreeSelector extends React.Component<TreeSelectorProps, TreeSelecto
return unfolded;
}
@autobind
toggleUnfolded(node: any) {
this.setState({
addItem: null,
unfolded: {
...this.state.unfolded,
[node[this.props.valueField as string]]: !this.state.unfolded[node[this.props.valueField as string]]
@ -164,6 +181,7 @@ export class TreeSelector extends React.Component<TreeSelectorProps, TreeSelecto
});
}
@autobind
clearSelect() {
this.setState(
{
@ -177,6 +195,7 @@ export class TreeSelector extends React.Component<TreeSelectorProps, TreeSelecto
);
}
@autobind
handleSelect(node: any, value?: any) {
this.setState(
{
@ -190,6 +209,7 @@ export class TreeSelector extends React.Component<TreeSelectorProps, TreeSelecto
);
}
@autobind
handleCheck(item: any, checked: boolean) {
const props = this.props;
const value = this.state.value.concat();
@ -273,6 +293,127 @@ export class TreeSelector extends React.Component<TreeSelectorProps, TreeSelecto
);
}
@autobind
handleAdd(item: Option | null, isFolder: boolean) {
const {addMode, openAddDialog, valueField} = this.props;
let {unfolded} = this.state;
if (addMode === 'dialog') {
openAddDialog && openAddDialog(item ? item : null)
} else if (addMode === 'normal') {
// item 为 null 时为添加一级
if (item) {
// 添加时,默认折叠的文件夹需要展开
if (isFolder && !unfolded[item[valueField as string]]) {
unfolded = {
...unfolded,
[item[valueField as string]]: !unfolded[item[valueField as string]],
}
}
this.setState({
addItem: item,
editItem: null,
unfolded
});
} else {
this.setState({
addTop: true,
editItem: null,
addItem: null
});
}
}
}
@autobind
handleEdit(item: Option) {
const {editMode, openEditDialog} = this.props;
const {addItem} = this.state;
if (editMode === 'dialog') {
openEditDialog && openEditDialog(item);
addItem && this.setState({
addItem: null
});
} else if (editMode === 'normal') {
this.setState({
editItem: item,
addItem: null
});
}
}
@autobind
handleRemove(item: Option) {
const {onRemove} = this.props;
onRemove && onRemove(item);
}
@autobind
handleConfirmOnAdd() {
const {onAdd} = this.props;
const {addItem: parent, addingItem} = this.state;
onAdd && onAdd({
...addingItem,
parent: parent
});
this.setState({
addingItem: null,
addItem: null,
addTop: false
})
}
@autobind
handleCancelOnAdd() {
this.setState({
addItem: null,
addTop: false
});
}
@autobind
handleConfirmOnEdit() {
const {onEdit} = this.props;
let {editingItem, editItem: prevItem} = this.state;
onEdit && onEdit({
...editingItem,
prev: prevItem
});
this.setState({
editingItem: null,
editItem: null
});
}
@autobind
handleCancelOnEdit() {
this.setState({
editItem: null
});
}
@autobind
handleChangeOnAdd(value: string) {
this.setState({
addingItem: {
label: value
}
});
}
@autobind
handleChangeOnEdit(item: Option, value: string) {
let {editItem} = this.state;
this.setState({
editingItem: {
...item,
label: value || (editItem as Option)['label']
}
});
}
@autobind
renderList(
list: Options,
value: Option[],
@ -295,8 +436,18 @@ export class TreeSelector extends React.Component<TreeSelectorProps, TreeSelecto
highlightTxt,
data,
maxLength,
minLength
minLength,
addable,
editable,
deletable
} = this.props;
const {
addItem,
editItem,
unfolded,
addTop,
value: stateValue
} = this.state;
let childrenChecked = 0;
let ret = list.map((item, key) => {
@ -333,8 +484,8 @@ export class TreeSelector extends React.Component<TreeSelectorProps, TreeSelecto
if (
!nodeDisabled &&
((maxLength && !selfChecked && this.state.value.length >= maxLength) ||
(minLength && selfChecked && this.state.value.length <= minLength))
((maxLength && !selfChecked && stateValue.length >= maxLength) ||
(minLength && selfChecked && stateValue.length <= minLength))
) {
nodeDisabled = true;
}
@ -370,47 +521,70 @@ export class TreeSelector extends React.Component<TreeSelectorProps, TreeSelecto
'Tree-item--isLeaf': isLeaf
})}
>
<a>
{!isLeaf ? (
<i
onClick={() => this.toggleUnfolded(item)}
className={cx('Tree-itemArrow', {
'is-folded': !this.state.unfolded[item[valueField]]
})}
/>
) : null}
{!editItem || isObject(editItem) && (editItem as Option)[valueField] !== item[valueField] ? (
<a>
{!isLeaf ? (
<i
onClick={() => this.toggleUnfolded(item)}
className={cx('Tree-itemArrow', {
'is-folded': !unfolded[item[valueField]],
})}
/>
) : null}
{showIcon ? (
<i
className={cx(
`Tree-itemIcon ${item[iconField] ||
{showIcon ? (
<i
className={cx(
`Tree-itemIcon ${item[iconField] ||
(childrenItems ? 'Tree-folderIcon' : 'Tree-leafIcon')}`
)}
/>
) : null}
)}
/>
) : null}
{checkbox}
{checkbox}
<span
className={cx('Tree-itemText', {
'is-children-checked': multiple && !cascade && tmpChildrenChecked && !nodeDisabled,
'is-checked': checked,
'is-disabled': nodeDisabled
})}
onClick={() =>
!nodeDisabled &&
(multiple ? this.handleCheck(item, !selfChecked) : this.handleSelect(item))
}
>
{highlightTxt ? highlight(item[nameField], highlightTxt) : item[nameField]}
</span>
</a>
{childrenItems ? (
<span
className={cx('Tree-itemText', {
'is-children-checked': multiple && !cascade && tmpChildrenChecked && !nodeDisabled,
'is-checked': checked,
'is-disabled': nodeDisabled,
})}
onClick={() =>
!nodeDisabled &&
(multiple ? this.handleCheck(item, !selfChecked) : this.handleSelect(item))
}
>
{highlightTxt ? highlight(item[nameField], highlightTxt) : item[nameField]}
</span>
{!addTop
&& !addItem
&& !editItem ? (
<span className={cx('Tree-item-icons')}>
{addable ? <Icon icon="plus" className="icon" onClick={() => this.handleAdd(item, !isLeaf)}/> : null}
{deletable ? <Icon icon="minus" className="icon" onClick={() => this.handleRemove(item)}/> : null}
{editable ? <Icon icon="pencil" className="icon" onClick={() => this.handleEdit(item)}/> : null}
</span>
) : null}
</a>
) : (
<div className={cx('Tree-item--isEdit')}>
<input defaultValue={item['label']} onChange={(e) => this.handleChangeOnEdit(item, e.currentTarget.value)}/>
<Icon icon="check" className="icon" onClick={this.handleConfirmOnEdit}/>
<Icon icon="close" className="icon" onClick={this.handleCancelOnEdit}/>
</div>
)}
{/* 有children而且为展开状态 或者 添加child时 */}
{((childrenItems && unfolded[item[valueField]]) || addItem && (addItem[valueField] === item[valueField])) ? (
<ul
className={cx('Tree-sublist', {
'is-folded': !this.state.unfolded[item[valueField]]
})}
className={cx('Tree-sublist')}
>
{addItem && addItem[valueField] === item[valueField] ? (
<li>
<input onChange={(e) => this.handleChangeOnAdd(e.currentTarget.value)}/>
<Icon icon="check" className="icon" onClick={this.handleConfirmOnAdd}/>
<Icon icon="close" className="icon" onClick={this.handleCancelOnAdd}/>
</li>
) : null}
{childrenItems}
</ul>
) : null}
@ -425,10 +599,10 @@ export class TreeSelector extends React.Component<TreeSelectorProps, TreeSelecto
}
render() {
const {className, placeholder, hideRoot, rootLabel, showIcon, classnames: cx} = this.props;
const {className, placeholder, hideRoot, rootLabel, showIcon, classnames: cx, addable} = this.props;
let data = this.props.data;
const {value, addTop} = this.state;
const value = this.state.value;
return (
<div className={cx(`Tree ${className || ''}`)}>
{data && data.length ? (
@ -450,6 +624,23 @@ export class TreeSelector extends React.Component<TreeSelectorProps, TreeSelecto
</span>
</label>
</a>
{addable ? (
<div className={cx('Tree-addTop')}>
{!addTop ? (
<p onClick={() => this.handleAdd(null, false)}>
<Icon icon="plus" className="icon" />
<span></span>
</p>
) : null}
{addTop ? (
<div className={cx('Tree-addTop-input')}>
<input onChange={(e) => this.handleChangeOnAdd(e.currentTarget.value)}/>
<Icon icon="check" className="icon" onClick={this.handleConfirmOnAdd}/>
<Icon icon="close" className="icon" onClick={this.handleCancelOnAdd}/>
</div>
) : null}
</div>
) : null}
<ul className={cx('Tree-sublist')}>{this.renderList(data, value, false).dom}</ul>
</li>
)}

View File

@ -25,6 +25,14 @@ import PauseIcon from '../icons/pause.svg';
import LeftArrowIcon from '../icons/left-arrow.svg';
// @ts-ignore
import RightArrowIcon from '../icons/right-arrow.svg';
// @ts-ignore
import CheckIcon from '../icons/check.svg';
// @ts-ignore
import PlusIcon from '../icons/plus.svg';
// @ts-ignore
import MinusIcon from '../icons/minus.svg';
// @ts-ignore
import PencilIcon from '../icons/pencil.svg';
// 兼容原来的用法,后续不直接试用。
// @ts-ignore
@ -70,6 +78,10 @@ registerIcon('play', PlayIcon);
registerIcon('pause', PauseIcon);
registerIcon('left-arrow', LeftArrowIcon);
registerIcon('right-arrow', RightArrowIcon);
registerIcon('check', CheckIcon);
registerIcon('plus', PlusIcon);
registerIcon('minus', MinusIcon);
registerIcon('pencil', PencilIcon);
export function Icon({
icon,
@ -91,5 +103,9 @@ export {
PlayIcon,
PauseIcon,
LeftArrowIcon,
RightArrowIcon
RightArrowIcon,
CheckIcon,
PlusIcon,
MinusIcon,
PencilIcon
};

3
src/icons/check.svg Normal file
View File

@ -0,0 +1,3 @@
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3506" xmlns:xlink="http://www.w3.org/1999/xlink">
<path d="M972.544 175.189333a31.658667 31.658667 0 0 1 15.701333 5.162667 31.872 31.872 0 0 1 7.296 46.976c-0.682667 0.896-1.493333 1.664-2.218666 2.474667L343.082667 876.032a32.341333 32.341333 0 0 1-37.546667 5.589333 37.504 37.504 0 0 1-8.064-6.101333L30.208 597.845333c-0.768-0.810667-1.536-1.621333-2.218667-2.517333a32.256 32.256 0 0 1 14.378667-49.749333 32 32 0 0 1 28.8 3.584c2.474667 1.664 2.944 2.304 5.12 4.309333l244.736 254.250667L948.181333 184.448l2.517334-2.261333a36.693333 36.693333 0 0 1 11.861333-6.016c2.901333-0.725333 3.669333-0.725333 6.613333-1.024l3.370667 0.042666z" p-id="3507"></path>
</svg>

After

Width:  |  Height:  |  Size: 759 B

1
src/icons/minus.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1568167473830" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3585" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M1024 511.994q0 9-6 15-6 5-14 5-328 0-984 0-11 0-16-7-4-7-4-13 0-7 6-14 6-7 14-7 123 0 369 0 205 0 615 0 8 0 14 6 6 6 6 15z" p-id="3586"></path></svg>

After

Width:  |  Height:  |  Size: 527 B

3
src/icons/pencil.svg Normal file
View File

@ -0,0 +1,3 @@
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1208" xmlns:xlink="http://www.w3.org/1999/xlink">
<path d="M302.026 783.023q-0.761 0-2.282 0.761 88.25 0 352.999 0 6.847 0 11.412 4.565 4.565 4.565 4.565 10.651 0 6.847-4.565 11.412-4.565 4.565-11.412 4.565-156.72 0-470.919 0 0 0-0.761 0 0 0-0.761 0-1.522 0-2.282-0.761 0 0-0.761 0 0 0-0.761 0 0 0 0 0 0 0-0.761-0.761-0.761 0-1.522-0.761-0.761 0-1.522-0.761 0 0-0.761-0.761-0.761-0.761-2.282-2.282-0.761-1.522-1.522-2.282-0.761-0.761-0.761-1.522 0 0 0-0.761 0-0.761-0.761-0.761 0-0.761 0-1.522 0-0.761 0-2.282 0 0 0 0 0 0 0-0.761 0 0 0-0.761 0 0 0-1.522 0 0 0-0.761 0 0 0-0.761 0 0 0.761 0 0 0 0-0.761 7.608-28.909 30.431-115.638 1.522-3.804 4.565-6.847 160.523-159.002 481.57-477.006 4.565-4.565 10.651-4.565 6.847 0 11.412 4.565 4.565 4.565 4.565 10.651 0 6.847-4.565 11.412-159.763 158.241-478.527 473.962-6.086 21.302-23.584 85.968 22.062-5.325 86.728-22.823 119.442-118.681 478.527-473.962 4.565-4.565 10.651-4.565 6.847 0 11.412 4.565 4.565 4.565 4.565 10.651 0 6.847-4.565 11.412-160.523 159.002-482.331 477.006-2.282 3.043-6.847 3.804zM823.918 269.5q0.761-0.761 3.043-3.043 3.043-3.043 6.086-6.847 0.761-0.761 3.043-3.043 22.823-22.062 22.823-53.254 0-31.192-22.823-53.254-22.062-22.062-53.254-22.062-31.192 0-53.254 22.062-4.565 4.565-12.933 12.933 26.627 26.627 107.269 106.508zM166.608 798.999q0 0 0 0.761 0 0 0-0.761zM166.608 796.717q0 0 0 1.522 0 0 0-1.522zM166.608 801.281q0 0 0-1.522 0 0 0 1.522zM167.369 795.195q-0.761 0-0.761 0.761 0 0 0-0.761 0 0 0.761 0z" p-id="1209"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

3
src/icons/plus.svg Normal file
View File

@ -0,0 +1,3 @@
<svg t="1568167179945" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2417" xmlns:xlink="http://www.w3.org/1999/xlink">
<path d="M925.313 493.255c0 12.866-10.421 23.285-23.285 23.285h-349.522v349.533c0 12.853-10.421 23.285-23.285 23.285s-23.285-10.432-23.285-23.285v-349.533h-349.522c-12.866 0-23.285-10.421-23.285-23.285 0-12.853 10.421-23.285 23.285-23.285h349.522v-349.522c0-12.866 10.421-23.285 23.285-23.285s23.285 10.421 23.285 23.285v349.522h349.522c12.866 0 23.285 10.432 23.285 23.285z" p-id="2418" fill="#707070"></path>
</svg>

After

Width:  |  Height:  |  Size: 586 B

View File

@ -2,6 +2,11 @@ import React from 'react';
import cx from 'classnames';
import TreeSelector from '../../components/Tree';
import {OptionsControl, OptionsControlProps} from './Options';
import {autobind, createObject} from '../../utils/helper';
import {Action, Schema, PlainObject, Api, Payload} from '../../types';
import {isEffectiveApi} from '../../utils/api';
import {filter} from '../../utils/tpl';
import {Option} from '../../components/Checkboxes';
export interface TreeProps extends OptionsControlProps {
placeholder?: any;
@ -12,9 +17,24 @@ export interface TreeProps extends OptionsControlProps {
cascade?: boolean; // 父子之间是否完全独立。
withChildren?: boolean; // 选父级的时候是否把子节点的值也包含在内。
onlyChildren?: boolean; // 选父级的时候,是否只把子节点的值包含在内
addApi?: Api;
addMode?: 'dialog' | 'normal';
addDialog?: Schema;
editApi?: Api;
editMode?: 'dialog' | 'normal';
editDialog?: Schema;
deleteApi?: Api;
deleteConfirmText?: string;
}
export default class TreeControl extends React.Component<TreeProps, any> {
export interface TreeState {
isAddModalOpened: boolean,
isEditModalOpened: boolean,
parent: Option | null,
prev: Option | null
}
export default class TreeControl extends React.Component<TreeProps, TreeState> {
static defaultProps: Partial<TreeProps> = {
placeholder: '选项加载中...',
multiple: false,
@ -24,11 +44,123 @@ export default class TreeControl extends React.Component<TreeProps, any> {
showIcon: true
};
state: TreeState = {
isAddModalOpened: false,
isEditModalOpened: false,
parent: null,
prev: null
}
reload() {
const reload = this.props.reloadOptions;
reload && reload();
}
@autobind
handleAdd(values: PlainObject) {
this.saveRemote(values, 'add');
}
@autobind
handleAddModalConfirm(values: Array<any>, action: Action, ctx: any, components: Array<any>) {
this.saveRemote({
...values,
parent: this.state.parent
}, 'add');
this.closeAddDialog();
}
@autobind
handleEdit(values: PlainObject) {
this.saveRemote(values, 'edit');
}
@autobind
handleEditModalConfirm(values: Array<any>, action: Action, ctx: any, components: Array<any>) {
this.saveRemote({
...values,
prev: this.state.prev
}, 'edit');
this.closeEditDialog();
}
@autobind
async saveRemote(item: any, type: 'add' | 'edit') {
const {
addApi,
editApi,
data,
env
} = this.props;
let remote: Payload | null = null;
if (type == 'add' && isEffectiveApi(addApi, createObject(data, item))) {
remote = await env.fetcher(addApi, createObject(data, item));
} else if (type == 'edit' && isEffectiveApi(editApi, createObject(data, item))) {
remote = await env.fetcher(editApi, createObject(data, item));
}
if (remote && !remote.ok) {
env.notify('error', remote.msg || '保存失败');
return;
}
this.reload();
}
@autobind
async handleRemove(item: any) {
const {deleteConfirmText, deleteApi, data, env} = this.props;
const ctx = createObject(data, item);
if (isEffectiveApi(deleteApi, ctx)) {
const confirmed = await env.confirm(deleteConfirmText ? filter(deleteConfirmText, ctx) : '确认要删除?');
if (!confirmed) {
return;
}
const result = await env.fetcher(deleteApi, ctx);
if (!result.ok) {
env.notify('error', '删除失败');
return;
}
this.reload();
}
}
@autobind
openAddDialog(parent: Option | null) {
this.setState({
isAddModalOpened: true,
parent
});
}
@autobind
closeAddDialog() {
this.setState({
isAddModalOpened: false,
parent: null
});
}
@autobind
openEditDialog(prev: Option) {
this.setState({
isEditModalOpened: true,
prev
})
}
@autobind
closeEditDialog() {
this.setState({
isEditModalOpened: false,
prev: null
});
}
render() {
const {
className,
@ -55,7 +187,14 @@ export default class TreeControl extends React.Component<TreeProps, any> {
rootValue,
showIcon,
showRadio,
render
render,
addMode,
addApi,
addDialog,
editMode,
editApi,
editDialog,
deleteApi
} = this.props;
return (
@ -90,8 +229,46 @@ export default class TreeControl extends React.Component<TreeProps, any> {
value={value || ''}
nameField="label"
selfDisabledAffectChildren={false}
addMode={addMode}
addable={isEffectiveApi(addApi)}
onAdd={this.handleAdd}
openAddDialog={this.openAddDialog}
editMode={editMode}
editable={isEffectiveApi(editApi)}
onEdit={this.handleEdit}
openEditDialog={this.openEditDialog}
onRemove={this.handleRemove}
deletable={isEffectiveApi(deleteApi)}
/>
)}
{addMode && render(
'modal',
{
type: 'dialog',
...addDialog
},
{
key: 'addModal',
onConfirm: this.handleAddModalConfirm,
onClose: this.closeAddDialog,
show: this.state.isAddModalOpened
}
)}
{editMode && render(
'modal',
{
type: 'dialog',
...editDialog
},
{
key: 'editModal',
onConfirm: this.handleEditModalConfirm,
onClose: this.closeEditDialog,
show: this.state.isEditModalOpened
}
)}
</div>
);
}