2022-06-15 19:25:55 +08:00
|
|
|
|
/**
|
|
|
|
|
* @file 组件选项组件的可视化编辑控件
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import React from 'react';
|
|
|
|
|
import {findDOMNode} from 'react-dom';
|
|
|
|
|
import cx from 'classnames';
|
|
|
|
|
import uniqBy from 'lodash/uniqBy';
|
|
|
|
|
import omit from 'lodash/omit';
|
2023-04-11 15:11:55 +08:00
|
|
|
|
import get from 'lodash/get';
|
2022-06-15 19:25:55 +08:00
|
|
|
|
import Sortable from 'sortablejs';
|
2023-04-03 15:37:16 +08:00
|
|
|
|
import {
|
|
|
|
|
FormItem,
|
|
|
|
|
Button,
|
|
|
|
|
Checkbox,
|
|
|
|
|
Icon,
|
|
|
|
|
InputBox,
|
|
|
|
|
render as amisRender
|
|
|
|
|
} from 'amis';
|
2022-06-15 19:25:55 +08:00
|
|
|
|
import {value2array} from 'amis-ui/lib/components/Select';
|
|
|
|
|
|
2023-01-17 17:56:53 +08:00
|
|
|
|
import {autobind, getI18nEnabled} from 'amis-editor-core';
|
2022-06-15 19:25:55 +08:00
|
|
|
|
import {getSchemaTpl} from 'amis-editor-core';
|
2022-07-05 23:06:46 +08:00
|
|
|
|
import {tipedLabel} from 'amis-editor-core';
|
2022-06-15 19:25:55 +08:00
|
|
|
|
|
|
|
|
|
import type {Option} from 'amis';
|
2023-04-11 15:11:55 +08:00
|
|
|
|
import {createObject, FormControlProps} from 'amis-core';
|
2022-06-15 19:25:55 +08:00
|
|
|
|
import type {OptionValue} from 'amis-core';
|
2023-05-05 17:13:06 +08:00
|
|
|
|
import type {SchemaApi} from 'amis';
|
2023-10-09 11:03:19 +08:00
|
|
|
|
import debounce from 'lodash/debounce';
|
2022-06-15 19:25:55 +08:00
|
|
|
|
|
|
|
|
|
export type valueType = 'text' | 'boolean' | 'number';
|
|
|
|
|
|
|
|
|
|
export interface PopoverForm {
|
|
|
|
|
optionLabel: string;
|
|
|
|
|
optionValue: any;
|
|
|
|
|
optionValueType: valueType;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type OptionControlItem = Option & {checked: boolean};
|
|
|
|
|
|
|
|
|
|
export interface OptionControlProps extends FormControlProps {
|
|
|
|
|
className?: string;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-05 14:01:27 +08:00
|
|
|
|
export type SourceType = 'custom' | 'api' | 'apicenter' | 'variable';
|
|
|
|
|
|
2022-06-15 19:25:55 +08:00
|
|
|
|
export interface OptionControlState {
|
|
|
|
|
options: Array<OptionControlItem>;
|
|
|
|
|
api: SchemaApi;
|
|
|
|
|
labelField: string;
|
|
|
|
|
valueField: string;
|
2023-05-05 14:01:27 +08:00
|
|
|
|
source: SourceType;
|
2022-06-15 19:25:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default class OptionControl extends React.Component<
|
|
|
|
|
OptionControlProps,
|
|
|
|
|
OptionControlState
|
|
|
|
|
> {
|
|
|
|
|
sortable?: Sortable;
|
|
|
|
|
drag?: HTMLElement | null;
|
|
|
|
|
target: HTMLElement | null;
|
|
|
|
|
$comp: string; // 记录一下路径,不再从外部同步内部,只从内部同步外部
|
|
|
|
|
|
|
|
|
|
internalProps = ['checked', 'editing'];
|
|
|
|
|
|
|
|
|
|
constructor(props: OptionControlProps) {
|
|
|
|
|
super(props);
|
|
|
|
|
|
2023-05-05 14:01:27 +08:00
|
|
|
|
let source: SourceType = 'custom';
|
2022-09-20 19:09:41 +08:00
|
|
|
|
|
2022-11-28 15:59:41 +08:00
|
|
|
|
if (props.data.hasOwnProperty('source') && props.data.source) {
|
2022-09-20 19:09:41 +08:00
|
|
|
|
const api = props.data.source;
|
|
|
|
|
const url =
|
|
|
|
|
typeof api === 'string'
|
|
|
|
|
? api
|
|
|
|
|
: typeof api === 'object'
|
|
|
|
|
? api.url || ''
|
|
|
|
|
: '';
|
|
|
|
|
|
2023-05-05 14:01:27 +08:00
|
|
|
|
source = /\$\{(.*?)\}/g.test(props.data.source)
|
|
|
|
|
? 'variable'
|
|
|
|
|
: !url.indexOf('api://')
|
|
|
|
|
? 'apicenter'
|
|
|
|
|
: 'api';
|
2022-09-20 19:09:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
2022-06-15 19:25:55 +08:00
|
|
|
|
this.state = {
|
|
|
|
|
options: this.transformOptions(props),
|
|
|
|
|
api: props.data.source,
|
|
|
|
|
labelField: props.data.labelField,
|
|
|
|
|
valueField: props.data.valueField,
|
2022-09-20 19:09:41 +08:00
|
|
|
|
source
|
2022-06-15 19:25:55 +08:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-11 15:11:55 +08:00
|
|
|
|
/**
|
|
|
|
|
* 数据更新
|
|
|
|
|
*/
|
|
|
|
|
componentWillReceiveProps(nextProps: OptionControlProps) {
|
|
|
|
|
const options = get(nextProps, 'data.options')
|
|
|
|
|
? this.transformOptions(nextProps)
|
|
|
|
|
: [];
|
|
|
|
|
if (
|
|
|
|
|
JSON.stringify(
|
|
|
|
|
this.state.options.map(item => ({
|
|
|
|
|
...item,
|
|
|
|
|
editing: undefined
|
|
|
|
|
}))
|
|
|
|
|
) !== JSON.stringify(options)
|
|
|
|
|
) {
|
|
|
|
|
this.setState({
|
|
|
|
|
options
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-15 19:25:55 +08:00
|
|
|
|
/**
|
|
|
|
|
* 获取当前选项值的类型
|
|
|
|
|
*/
|
|
|
|
|
getOptionValueType(value: any): valueType {
|
|
|
|
|
if (typeof value === 'string') {
|
|
|
|
|
return 'text';
|
|
|
|
|
}
|
|
|
|
|
if (typeof value === 'boolean') {
|
|
|
|
|
return 'boolean';
|
|
|
|
|
}
|
|
|
|
|
if (typeof value === 'number') {
|
|
|
|
|
return 'number';
|
|
|
|
|
}
|
|
|
|
|
return 'text';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 将当前选项值转换为选择的类型
|
|
|
|
|
*/
|
|
|
|
|
normalizeOptionValue(value: any, valueType: valueType) {
|
|
|
|
|
if (valueType === 'text') {
|
|
|
|
|
return String(value);
|
|
|
|
|
}
|
|
|
|
|
if (valueType === 'number') {
|
|
|
|
|
const convertTo = Number(value);
|
|
|
|
|
if (isNaN(convertTo)) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
return convertTo;
|
|
|
|
|
}
|
|
|
|
|
if (valueType === 'boolean') {
|
|
|
|
|
return !value || value === 'false' ? false : true;
|
|
|
|
|
}
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理填入输入框的值
|
|
|
|
|
*/
|
|
|
|
|
transformOptionValue(value: any) {
|
|
|
|
|
return typeof value === 'undefined' || value === null
|
|
|
|
|
? ''
|
|
|
|
|
: typeof value === 'string'
|
|
|
|
|
? value
|
|
|
|
|
: JSON.stringify(value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
transformOptions(props: OptionControlProps) {
|
|
|
|
|
const {data: ctx, value: options} = props;
|
|
|
|
|
let defaultValue: Array<OptionValue> | OptionValue = ctx.value;
|
|
|
|
|
|
|
|
|
|
const valueArray = value2array(defaultValue, ctx as any).map(
|
|
|
|
|
(item: Option) => item[ctx?.valueField ?? 'value']
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return Array.isArray(options)
|
|
|
|
|
? options.map((item: Option) => ({
|
|
|
|
|
label: item.label,
|
|
|
|
|
// 为了使用户编写label时同时生效到value
|
|
|
|
|
value: item.label === item.value ? null : item.value,
|
2023-04-11 15:11:55 +08:00
|
|
|
|
checked: !!~valueArray.indexOf(item[ctx?.valueField ?? 'value']),
|
|
|
|
|
...(item?.badge ? {badge: item.badge} : {}),
|
|
|
|
|
...(item.hidden !== undefined ? {hidden: item.hidden} : {}),
|
|
|
|
|
...(item.hiddenOn !== undefined ? {hiddenOn: item.hiddenOn} : {})
|
2022-06-15 19:25:55 +08:00
|
|
|
|
}))
|
|
|
|
|
: [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理当前组件的默认值
|
|
|
|
|
*/
|
|
|
|
|
normalizeValue() {
|
|
|
|
|
const {data: ctx = {}, multiple: multipleProps} = this.props;
|
|
|
|
|
const {
|
|
|
|
|
joinValues = true,
|
|
|
|
|
extractValue,
|
|
|
|
|
multiple,
|
|
|
|
|
delimiter,
|
|
|
|
|
valueField
|
|
|
|
|
} = ctx;
|
|
|
|
|
const checkedOptions = this.state.options
|
2023-04-11 15:11:55 +08:00
|
|
|
|
.filter(item => item.checked && item?.hidden !== true)
|
2022-06-15 19:25:55 +08:00
|
|
|
|
.map(item => omit(item, this.internalProps));
|
|
|
|
|
let value: Array<OptionValue> | OptionValue;
|
|
|
|
|
|
|
|
|
|
if (!checkedOptions.length) {
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (multiple || multipleProps) {
|
|
|
|
|
value = checkedOptions;
|
|
|
|
|
|
|
|
|
|
if (joinValues) {
|
|
|
|
|
value = checkedOptions
|
|
|
|
|
.map(
|
|
|
|
|
(item: any) =>
|
|
|
|
|
item[valueField || 'value'] || item[valueField || 'label']
|
|
|
|
|
)
|
|
|
|
|
.join(delimiter || ',');
|
|
|
|
|
} else if (extractValue) {
|
|
|
|
|
value = checkedOptions.map(
|
|
|
|
|
(item: Option) =>
|
|
|
|
|
item[valueField || 'value'] || item[valueField || 'label']
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
value = checkedOptions[0];
|
|
|
|
|
|
|
|
|
|
if (joinValues || extractValue) {
|
|
|
|
|
value = value[valueField || 'value'] || value[valueField || 'label'];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 更新options字段的统一出口
|
|
|
|
|
*/
|
|
|
|
|
onChange() {
|
|
|
|
|
const {source} = this.state;
|
|
|
|
|
const {onBulkChange} = this.props;
|
|
|
|
|
const defaultValue = this.normalizeValue();
|
|
|
|
|
const data: Partial<OptionControlProps> = {
|
|
|
|
|
source: undefined,
|
|
|
|
|
options: undefined,
|
|
|
|
|
labelField: undefined,
|
|
|
|
|
valueField: undefined
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (source === 'custom') {
|
|
|
|
|
const {options} = this.state;
|
|
|
|
|
data.options = options.map(item => ({
|
2023-04-11 15:11:55 +08:00
|
|
|
|
...(item?.badge ? {badge: item.badge} : {}),
|
2022-06-15 19:25:55 +08:00
|
|
|
|
label: item.label,
|
2023-04-11 15:11:55 +08:00
|
|
|
|
value:
|
|
|
|
|
item.value == null || item.value === '' ? item.label : item.value,
|
|
|
|
|
...(item.hiddenOn !== undefined ? {hiddenOn: item.hiddenOn} : {})
|
2022-06-15 19:25:55 +08:00
|
|
|
|
}));
|
2023-04-11 15:11:55 +08:00
|
|
|
|
data.value = defaultValue;
|
2022-06-15 19:25:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
2023-05-05 14:01:27 +08:00
|
|
|
|
if (source === 'api' || source === 'apicenter' || source === 'variable') {
|
2022-06-15 19:25:55 +08:00
|
|
|
|
const {api, labelField, valueField} = this.state;
|
|
|
|
|
data.source = api;
|
2022-12-09 19:47:30 +08:00
|
|
|
|
data.labelField = labelField || undefined;
|
|
|
|
|
data.valueField = valueField || undefined;
|
2022-06-15 19:25:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onBulkChange && onBulkChange(data);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@autobind
|
|
|
|
|
targetRef(ref: any) {
|
|
|
|
|
this.target = ref ? (findDOMNode(ref) as HTMLElement) : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@autobind
|
|
|
|
|
dragRef(ref: any) {
|
|
|
|
|
if (!this.drag && ref) {
|
|
|
|
|
this.initDragging();
|
|
|
|
|
} else if (this.drag && !ref) {
|
|
|
|
|
this.destroyDragging();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.drag = ref;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
initDragging() {
|
|
|
|
|
const dom = findDOMNode(this) as HTMLElement;
|
|
|
|
|
|
|
|
|
|
this.sortable = new Sortable(
|
|
|
|
|
dom.querySelector('.ae-OptionControl-content') as HTMLElement,
|
|
|
|
|
{
|
|
|
|
|
group: 'OptionControlGroup',
|
|
|
|
|
animation: 150,
|
|
|
|
|
handle: '.ae-OptionControlItem-dragBar',
|
|
|
|
|
ghostClass: 'ae-OptionControlItem--dragging',
|
|
|
|
|
onEnd: (e: any) => {
|
|
|
|
|
// 没有移动
|
|
|
|
|
if (e.newIndex === e.oldIndex) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 换回来
|
|
|
|
|
const parent = e.to as HTMLElement;
|
|
|
|
|
if (
|
|
|
|
|
e.newIndex < e.oldIndex &&
|
|
|
|
|
e.oldIndex < parent.childNodes.length - 1
|
|
|
|
|
) {
|
|
|
|
|
parent.insertBefore(e.item, parent.childNodes[e.oldIndex + 1]);
|
|
|
|
|
} else if (e.oldIndex < parent.childNodes.length - 1) {
|
|
|
|
|
parent.insertBefore(e.item, parent.childNodes[e.oldIndex]);
|
|
|
|
|
} else {
|
|
|
|
|
parent.appendChild(e.item);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const options = this.state.options.concat();
|
|
|
|
|
|
|
|
|
|
options[e.oldIndex] = options.splice(
|
|
|
|
|
e.newIndex,
|
|
|
|
|
1,
|
|
|
|
|
options[e.oldIndex]
|
|
|
|
|
)[0];
|
|
|
|
|
|
|
|
|
|
this.setState({options}, () => this.onChange());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
destroyDragging() {
|
|
|
|
|
this.sortable && this.sortable.destroy();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
scroll2Bottom() {
|
|
|
|
|
this.drag &&
|
|
|
|
|
this.drag?.lastElementChild?.scrollIntoView({
|
|
|
|
|
behavior: 'smooth',
|
|
|
|
|
block: 'nearest',
|
|
|
|
|
inline: 'start'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 切换选项类型
|
|
|
|
|
*/
|
|
|
|
|
@autobind
|
2023-05-05 14:01:27 +08:00
|
|
|
|
handleSourceChange(source: SourceType) {
|
|
|
|
|
this.setState({api: '', source: source}, this.onChange);
|
2022-06-15 19:25:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 删除选项
|
|
|
|
|
*/
|
|
|
|
|
handleDelete(index: number) {
|
|
|
|
|
const options = this.state.options.concat();
|
|
|
|
|
|
|
|
|
|
options.splice(index, 1);
|
|
|
|
|
this.setState({options}, () => this.onChange());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 设置默认选项
|
|
|
|
|
*/
|
|
|
|
|
handleToggleDefaultValue(index: number, checked: any, shift?: boolean) {
|
|
|
|
|
let options = this.state.options.concat();
|
|
|
|
|
const isMultiple = this.props?.data?.multiple || this.props?.multiple;
|
|
|
|
|
|
|
|
|
|
if (isMultiple) {
|
|
|
|
|
options.splice(index, 1, {...options[index], checked});
|
|
|
|
|
} else {
|
|
|
|
|
options = options.map((item, itemIndex) => ({
|
|
|
|
|
...item,
|
2022-07-07 11:24:03 +08:00
|
|
|
|
checked: itemIndex === index ? checked : false // 支持重复点击取消选中
|
2022-06-15 19:25:55 +08:00
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.setState({options}, () => this.onChange());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 编辑选项
|
|
|
|
|
*/
|
|
|
|
|
toggleEdit(index: number) {
|
|
|
|
|
const {options} = this.state;
|
|
|
|
|
options[index].editing = !options[index].editing;
|
|
|
|
|
this.setState({options});
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-11 15:11:55 +08:00
|
|
|
|
/**
|
|
|
|
|
* 编辑角标
|
|
|
|
|
*/
|
2023-04-13 16:44:18 +08:00
|
|
|
|
toggleBadge(index: number, value: string) {
|
2023-04-11 15:11:55 +08:00
|
|
|
|
const {options} = this.state;
|
2023-04-13 16:44:18 +08:00
|
|
|
|
options[index].badge = value;
|
2023-04-11 15:11:55 +08:00
|
|
|
|
|
|
|
|
|
this.setState({options}, () => this.onChange());
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-15 19:25:55 +08:00
|
|
|
|
@autobind
|
|
|
|
|
handleEditLabel(index: number, value: string) {
|
|
|
|
|
const options = this.state.options.concat();
|
|
|
|
|
options.splice(index, 1, {...options[index], label: value});
|
|
|
|
|
this.setState({options}, () => this.onChange());
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-11 15:11:55 +08:00
|
|
|
|
@autobind
|
|
|
|
|
handleHiddenValueChange(index: number, value: string) {
|
|
|
|
|
const options = this.state.options.concat();
|
|
|
|
|
const {hiddenOn, ...option} = options[index];
|
|
|
|
|
options.splice(index, 1, {
|
|
|
|
|
...option,
|
|
|
|
|
...(!value ? {} : {hiddenOn: value})
|
|
|
|
|
});
|
|
|
|
|
this.setState({options}, () => this.onChange());
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-15 19:25:55 +08:00
|
|
|
|
@autobind
|
|
|
|
|
handleAdd() {
|
|
|
|
|
const {options} = this.state;
|
|
|
|
|
options.push({
|
|
|
|
|
label: '',
|
|
|
|
|
value: null,
|
|
|
|
|
checked: false
|
|
|
|
|
});
|
|
|
|
|
this.setState({options}, () => {
|
|
|
|
|
this.onChange();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
handleValueTypeChange(index: number, type: valueType) {
|
|
|
|
|
const options = this.state.options.concat();
|
|
|
|
|
options[index].value = this.normalizeOptionValue(
|
|
|
|
|
options[index].value,
|
|
|
|
|
type
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
this.setState({options}, () => this.onChange());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
handleValueChange(index: number, value: string) {
|
|
|
|
|
const options = this.state.options.concat();
|
|
|
|
|
const type = this.getOptionValueType(options[index].value);
|
|
|
|
|
options[index].value = this.normalizeOptionValue(value, type);
|
|
|
|
|
|
|
|
|
|
this.setState({options}, () => this.onChange());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@autobind
|
2023-04-11 15:11:55 +08:00
|
|
|
|
handleBatchAdd(values: {batchOption: string}[], action: any) {
|
2022-06-15 19:25:55 +08:00
|
|
|
|
const options = this.state.options.concat();
|
2023-04-11 15:11:55 +08:00
|
|
|
|
const addedOptions: Array<OptionControlItem> = values[0].batchOption
|
2022-06-15 19:25:55 +08:00
|
|
|
|
.split('\n')
|
|
|
|
|
.map(option => {
|
|
|
|
|
const item = option.trim();
|
|
|
|
|
if (~item.indexOf(' ')) {
|
|
|
|
|
let [label, value] = item.split(' ');
|
|
|
|
|
return {label: label.trim(), value: value.trim(), checked: false};
|
|
|
|
|
}
|
|
|
|
|
return {label: item, value: item, checked: false};
|
|
|
|
|
});
|
|
|
|
|
const newOptions = uniqBy([...options, ...addedOptions], 'value');
|
|
|
|
|
|
|
|
|
|
this.setState({options: newOptions}, () => this.onChange());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
renderHeader() {
|
2022-08-04 11:16:57 +08:00
|
|
|
|
const {
|
|
|
|
|
render,
|
|
|
|
|
label,
|
|
|
|
|
labelRemark,
|
|
|
|
|
useMobileUI,
|
|
|
|
|
env,
|
|
|
|
|
popOverContainer,
|
|
|
|
|
hasApiCenter
|
|
|
|
|
} = this.props;
|
2022-06-15 19:25:55 +08:00
|
|
|
|
const classPrefix = env?.theme?.classPrefix;
|
|
|
|
|
const {source} = this.state;
|
2022-07-13 14:44:51 +08:00
|
|
|
|
const optionSourceList = (
|
|
|
|
|
[
|
|
|
|
|
{
|
|
|
|
|
label: '自定义选项',
|
|
|
|
|
value: 'custom'
|
|
|
|
|
},
|
|
|
|
|
{
|
2022-08-04 11:16:57 +08:00
|
|
|
|
label: '外部接口',
|
2022-07-13 14:44:51 +08:00
|
|
|
|
value: 'api'
|
2022-08-04 11:16:57 +08:00
|
|
|
|
},
|
2023-05-05 14:01:27 +08:00
|
|
|
|
...(hasApiCenter ? [{label: 'API中心', value: 'apicenter'}] : []),
|
|
|
|
|
{
|
|
|
|
|
label: '上下文变量',
|
|
|
|
|
value: 'variable'
|
|
|
|
|
}
|
2022-07-13 14:44:51 +08:00
|
|
|
|
// {
|
|
|
|
|
// label: '表单实体',
|
|
|
|
|
// value: 'form'
|
|
|
|
|
// }
|
|
|
|
|
] as Array<{
|
|
|
|
|
label: string;
|
2023-05-05 14:01:27 +08:00
|
|
|
|
value: SourceType;
|
2022-07-13 14:44:51 +08:00
|
|
|
|
}>
|
|
|
|
|
).map(item => ({
|
2022-06-15 19:25:55 +08:00
|
|
|
|
...item,
|
|
|
|
|
onClick: () => this.handleSourceChange(item.value)
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<header className="ae-OptionControl-header">
|
|
|
|
|
<label className={cx(`${classPrefix}Form-label`)}>
|
|
|
|
|
{label || ''}
|
|
|
|
|
{labelRemark
|
|
|
|
|
? render('label-remark', {
|
|
|
|
|
type: 'remark',
|
|
|
|
|
icon: labelRemark.icon || 'warning-mark',
|
|
|
|
|
tooltip: labelRemark,
|
|
|
|
|
className: cx(`Form-lableRemark`, labelRemark?.className),
|
|
|
|
|
useMobileUI,
|
2023-07-06 20:17:47 +08:00
|
|
|
|
container: popOverContainer || env.getModalContainer
|
2022-06-15 19:25:55 +08:00
|
|
|
|
})
|
|
|
|
|
: null}
|
|
|
|
|
</label>
|
|
|
|
|
<div>
|
|
|
|
|
{render(
|
|
|
|
|
'validation-control-addBtn',
|
|
|
|
|
{
|
|
|
|
|
type: 'dropdown-button',
|
|
|
|
|
level: 'link',
|
|
|
|
|
size: 'sm',
|
|
|
|
|
label: '${selected}',
|
|
|
|
|
align: 'right',
|
|
|
|
|
closeOnClick: true,
|
|
|
|
|
closeOnOutside: true,
|
|
|
|
|
buttons: optionSourceList
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
popOverContainer: null,
|
|
|
|
|
data: {
|
|
|
|
|
selected: optionSourceList.find(item => item.value === source)!
|
|
|
|
|
.label
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</header>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
renderOption(props: any) {
|
2022-09-06 19:43:03 +08:00
|
|
|
|
const {checked, index, editing, multipleProps, closeDefaultCheck} = props;
|
2023-04-11 15:11:55 +08:00
|
|
|
|
const {render, data: ctx, node} = this.props;
|
2022-06-15 19:25:55 +08:00
|
|
|
|
const isMultiple = ctx?.multiple === true || multipleProps;
|
2023-01-17 17:56:53 +08:00
|
|
|
|
const i18nEnabled = getI18nEnabled();
|
2022-06-15 19:25:55 +08:00
|
|
|
|
const label = this.transformOptionValue(props.label);
|
|
|
|
|
const value = this.transformOptionValue(props.value);
|
|
|
|
|
const valueType = this.getOptionValueType(props.value);
|
2023-04-11 15:11:55 +08:00
|
|
|
|
const showBadge = node.type === 'button-group-select';
|
2022-06-15 19:25:55 +08:00
|
|
|
|
|
|
|
|
|
const editDom = editing ? (
|
|
|
|
|
<div className="ae-OptionControlItem-extendMore">
|
2023-04-11 15:11:55 +08:00
|
|
|
|
{render(
|
|
|
|
|
'option',
|
|
|
|
|
{
|
|
|
|
|
type: 'container',
|
|
|
|
|
className: 'ae-ExtendMore right mb-2',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'button',
|
|
|
|
|
className: 'ae-OptionControlItem-closeBtn',
|
|
|
|
|
label: '×',
|
|
|
|
|
level: 'link',
|
|
|
|
|
onClick: () => this.toggleEdit(index)
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: i18nEnabled ? 'input-text-i18n' : 'input-text',
|
|
|
|
|
placeholder: '请输入显示文本',
|
|
|
|
|
label: '文本',
|
|
|
|
|
mode: 'horizontal',
|
|
|
|
|
value: label,
|
|
|
|
|
name: 'optionLabel',
|
|
|
|
|
labelClassName: 'ae-OptionControlItem-EditLabel',
|
|
|
|
|
valueClassName: 'ae-OptionControlItem-EditValue',
|
|
|
|
|
onChange: (v: string) => this.handleEditLabel(index, v)
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: 'input-group',
|
|
|
|
|
name: 'input-group',
|
|
|
|
|
label: '值',
|
|
|
|
|
labelClassName: 'ae-OptionControlItem-EditLabel',
|
|
|
|
|
valueClassName: 'ae-OptionControlItem-EditValue',
|
|
|
|
|
mode: 'horizontal',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'select',
|
|
|
|
|
name: 'optionValueType',
|
|
|
|
|
value: valueType,
|
|
|
|
|
options: [
|
|
|
|
|
{
|
|
|
|
|
label: '文本',
|
|
|
|
|
value: 'text'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: '数字',
|
|
|
|
|
value: 'number'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: '布尔',
|
|
|
|
|
value: 'boolean'
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
checkAll: false,
|
|
|
|
|
onChange: (v: valueType) =>
|
|
|
|
|
this.handleValueTypeChange(index, v)
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: 'input-text',
|
|
|
|
|
placeholder: '默认与文本一致',
|
|
|
|
|
name: 'optionValue',
|
|
|
|
|
value,
|
|
|
|
|
visibleOn: "this.optionValueType !== 'boolean'",
|
|
|
|
|
onChange: (v: string) => this.handleValueChange(index, v)
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: 'input-text',
|
|
|
|
|
placeholder: '默认与文本一致',
|
|
|
|
|
name: 'optionValue',
|
|
|
|
|
value,
|
|
|
|
|
visibleOn: "this.optionValueType === 'boolean'",
|
|
|
|
|
onChange: (v: string) => this.handleValueChange(index, v),
|
|
|
|
|
options: [
|
|
|
|
|
{label: 'true', value: true},
|
|
|
|
|
{label: 'false', value: false}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
},
|
2023-06-12 18:56:18 +08:00
|
|
|
|
getSchemaTpl('expressionFormulaControl', {
|
2023-04-11 15:11:55 +08:00
|
|
|
|
name: 'optionHiddenOn',
|
|
|
|
|
label: '隐藏',
|
|
|
|
|
labelClassName: 'ae-OptionControlItem-EditLabel',
|
|
|
|
|
valueClassName: 'ae-OptionControlItem-EditValue',
|
|
|
|
|
onChange: (v: string) => this.handleHiddenValueChange(index, v)
|
2023-06-12 18:56:18 +08:00
|
|
|
|
}),
|
2023-04-11 15:11:55 +08:00
|
|
|
|
{
|
2023-04-13 16:44:18 +08:00
|
|
|
|
type: i18nEnabled ? 'input-text-i18n' : 'input-text',
|
|
|
|
|
placeholder: '请输入角标文本',
|
|
|
|
|
label: '角标',
|
|
|
|
|
mode: 'horizontal',
|
2023-04-11 15:11:55 +08:00
|
|
|
|
visible: showBadge,
|
|
|
|
|
value: props?.badge,
|
2023-04-13 16:44:18 +08:00
|
|
|
|
name: 'optionBadge',
|
|
|
|
|
labelClassName: 'ae-OptionControlItem-EditLabel',
|
|
|
|
|
valueClassName: 'ae-OptionControlItem-EditValue',
|
|
|
|
|
onChange: (v: string) => this.toggleBadge(index, v)
|
2023-04-11 15:11:55 +08:00
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{data: createObject(ctx, {option: props})}
|
|
|
|
|
)}
|
2022-06-15 19:25:55 +08:00
|
|
|
|
</div>
|
|
|
|
|
) : null;
|
|
|
|
|
|
2022-07-07 11:24:03 +08:00
|
|
|
|
const operationBtn = [
|
|
|
|
|
{
|
|
|
|
|
type: 'button',
|
|
|
|
|
className: 'ae-OptionControlItem-action',
|
|
|
|
|
label: '编辑',
|
|
|
|
|
onClick: () => this.toggleEdit(index)
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: 'button',
|
|
|
|
|
className: 'ae-OptionControlItem-action',
|
|
|
|
|
label: '删除',
|
|
|
|
|
onClick: () => this.handleDelete(index)
|
|
|
|
|
}
|
2022-07-13 14:44:51 +08:00
|
|
|
|
];
|
2022-07-07 11:24:03 +08:00
|
|
|
|
|
2022-07-07 11:30:50 +08:00
|
|
|
|
// 单选模式,选中时增加取消操作
|
|
|
|
|
if (!closeDefaultCheck && !isMultiple && checked) {
|
2022-07-07 11:24:03 +08:00
|
|
|
|
operationBtn.unshift({
|
|
|
|
|
type: 'button',
|
|
|
|
|
className: 'ae-OptionControlItem-action',
|
|
|
|
|
label: '取消选中',
|
|
|
|
|
onClick: () => this.handleToggleDefaultValue(index, false)
|
2022-07-13 14:44:51 +08:00
|
|
|
|
});
|
2022-07-07 11:24:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
2023-04-11 15:11:55 +08:00
|
|
|
|
const disabled = props?.hidden === true;
|
|
|
|
|
const tooltip = disabled ? '隐藏选项不能设为默认值' : '默认选中此项';
|
|
|
|
|
|
2022-06-15 19:25:55 +08:00
|
|
|
|
return (
|
|
|
|
|
<li className="ae-OptionControlItem" key={index}>
|
|
|
|
|
<div className="ae-OptionControlItem-Main">
|
|
|
|
|
<a className="ae-OptionControlItem-dragBar">
|
|
|
|
|
<Icon icon="drag-bar" className="icon" />
|
|
|
|
|
</a>
|
|
|
|
|
{!this.props.closeDefaultCheck &&
|
|
|
|
|
this.props.data.defaultCheckAll !== true && (
|
2023-04-11 15:11:55 +08:00
|
|
|
|
<span className="inline-flex" data-tooltip={tooltip}>
|
2022-06-15 19:25:55 +08:00
|
|
|
|
<Checkbox
|
|
|
|
|
className="ae-OptionControlItem-checkbox"
|
|
|
|
|
checked={checked}
|
2023-04-11 15:11:55 +08:00
|
|
|
|
disabled={disabled}
|
2022-06-15 19:25:55 +08:00
|
|
|
|
type={isMultiple ? 'checkbox' : 'radio'}
|
2022-07-13 14:44:51 +08:00
|
|
|
|
onChange={(newChecked: any, shift?: boolean) =>
|
|
|
|
|
this.handleToggleDefaultValue(index, newChecked, shift)
|
|
|
|
|
}
|
2022-06-15 19:25:55 +08:00
|
|
|
|
/>
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
2023-01-14 20:33:48 +08:00
|
|
|
|
{/* <InputBox
|
2022-06-15 19:25:55 +08:00
|
|
|
|
className="ae-OptionControlItem-input"
|
|
|
|
|
value={label}
|
2022-06-17 10:38:26 +08:00
|
|
|
|
placeholder="请输入文本/值"
|
2022-06-15 19:25:55 +08:00
|
|
|
|
clearable={false}
|
2022-07-05 23:06:46 +08:00
|
|
|
|
onChange={(value: string) => this.handleEditLabel(index, value)}
|
2023-01-14 20:33:48 +08:00
|
|
|
|
/> */}
|
2023-04-03 15:37:16 +08:00
|
|
|
|
{amisRender({
|
|
|
|
|
type: i18nEnabled ? 'input-text-i18n' : 'input-text',
|
|
|
|
|
className: 'ae-OptionControlItem-input',
|
|
|
|
|
value: label,
|
|
|
|
|
placeholder: '请输入文本/值',
|
|
|
|
|
clearable: false,
|
|
|
|
|
onChange: (value: string) => {
|
|
|
|
|
this.handleEditLabel(index, value);
|
|
|
|
|
}
|
|
|
|
|
})}
|
2022-09-23 19:16:58 +08:00
|
|
|
|
{render(
|
|
|
|
|
'dropdown',
|
|
|
|
|
{
|
|
|
|
|
type: 'dropdown-button',
|
|
|
|
|
className: 'ae-OptionControlItem-dropdown',
|
|
|
|
|
btnClassName: 'px-2',
|
|
|
|
|
icon: 'fa fa-ellipsis-h',
|
|
|
|
|
hideCaret: true,
|
|
|
|
|
closeOnClick: true,
|
|
|
|
|
align: 'right',
|
|
|
|
|
menuClassName: 'ae-OptionControlItem-ulmenu',
|
|
|
|
|
buttons: operationBtn
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
popOverContainer: null // amis 渲染挂载节点会使用 this.target
|
|
|
|
|
}
|
|
|
|
|
)}
|
2022-06-15 19:25:55 +08:00
|
|
|
|
</div>
|
|
|
|
|
{editDom}
|
|
|
|
|
</li>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
buildBatchAddSchema() {
|
|
|
|
|
return {
|
|
|
|
|
type: 'action',
|
|
|
|
|
actionType: 'dialog',
|
|
|
|
|
label: '批量添加',
|
|
|
|
|
dialog: {
|
|
|
|
|
title: '批量添加选项',
|
|
|
|
|
headerClassName: 'font-bold',
|
|
|
|
|
closeOnEsc: true,
|
|
|
|
|
closeOnOutside: false,
|
|
|
|
|
showCloseButton: true,
|
2023-04-11 15:11:55 +08:00
|
|
|
|
onConfirm: this.handleBatchAdd,
|
2022-06-15 19:25:55 +08:00
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'alert',
|
|
|
|
|
level: 'warning',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'tpl',
|
2022-07-13 14:44:51 +08:00
|
|
|
|
tpl: '每个选项单列一行,将所有值不重复的项加为新的选项;<br/>每行可通过空格来分别设置label和value,例:"张三 zhangsan"'
|
2022-06-15 19:25:55 +08:00
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
showIcon: true,
|
|
|
|
|
className: 'mb-2.5'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: 'form',
|
|
|
|
|
wrapWithPanel: false,
|
|
|
|
|
mode: 'normal',
|
|
|
|
|
wrapperComponent: 'div',
|
|
|
|
|
resetAfterSubmit: true,
|
|
|
|
|
autoFocus: true,
|
|
|
|
|
preventEnterSubmit: true,
|
|
|
|
|
horizontal: {
|
|
|
|
|
left: 0,
|
|
|
|
|
right: 12
|
|
|
|
|
},
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
name: 'batchOption',
|
|
|
|
|
type: 'textarea',
|
|
|
|
|
label: '',
|
|
|
|
|
placeholder: '请输入选项内容',
|
|
|
|
|
trimContents: true,
|
|
|
|
|
minRows: 10,
|
|
|
|
|
maxRows: 50,
|
|
|
|
|
required: true
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@autobind
|
|
|
|
|
handleAPIChange(source: SchemaApi) {
|
|
|
|
|
this.setState({api: source}, this.onChange);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@autobind
|
|
|
|
|
handleLableFieldChange(labelField: string) {
|
|
|
|
|
this.setState({labelField}, this.onChange);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@autobind
|
|
|
|
|
handleValueFieldChange(valueField: string) {
|
|
|
|
|
this.setState({valueField}, this.onChange);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-11 21:04:01 +08:00
|
|
|
|
/** 获取功能性字段控件 schema */
|
|
|
|
|
getFuncFieldSchema(): Record<string, any>[] {
|
|
|
|
|
const {labelField, valueField} = this.state;
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
label: tipedLabel(
|
|
|
|
|
'显示字段',
|
|
|
|
|
'选项文本对应的数据字段,多字段合并请通过模板配置'
|
|
|
|
|
),
|
|
|
|
|
type: 'input-text',
|
|
|
|
|
name: 'labelField',
|
|
|
|
|
clearable: true,
|
|
|
|
|
value: labelField,
|
|
|
|
|
placeholder: '选项文本对应的字段',
|
|
|
|
|
onChange: this.handleLableFieldChange
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: '值字段',
|
|
|
|
|
type: 'input-text',
|
|
|
|
|
name: 'valueField',
|
|
|
|
|
clearable: true,
|
|
|
|
|
value: valueField,
|
|
|
|
|
placeholder: '值对应的字段',
|
|
|
|
|
onChange: this.handleValueFieldChange
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-15 19:25:55 +08:00
|
|
|
|
renderApiPanel() {
|
|
|
|
|
const {render} = this.props;
|
2023-09-11 21:04:01 +08:00
|
|
|
|
const {source, api} = this.state;
|
2022-06-15 19:25:55 +08:00
|
|
|
|
|
|
|
|
|
return render(
|
|
|
|
|
'api',
|
|
|
|
|
getSchemaTpl('apiControl', {
|
|
|
|
|
label: '接口',
|
|
|
|
|
name: 'source',
|
2022-07-05 23:06:46 +08:00
|
|
|
|
mode: 'normal',
|
2022-06-15 19:25:55 +08:00
|
|
|
|
className: 'ae-ExtendMore',
|
|
|
|
|
visibleOn: 'data.autoComplete !== false',
|
|
|
|
|
value: api,
|
|
|
|
|
onChange: this.handleAPIChange,
|
2022-08-04 11:16:57 +08:00
|
|
|
|
sourceType: source,
|
2023-09-11 21:04:01 +08:00
|
|
|
|
footer: this.getFuncFieldSchema()
|
2022-06-15 19:25:55 +08:00
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
render() {
|
|
|
|
|
const {options, source} = this.state;
|
|
|
|
|
const {render, className, multiple: multipleProps} = this.props;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className={cx('ae-OptionControl', className)}>
|
|
|
|
|
{this.renderHeader()}
|
|
|
|
|
|
2023-09-11 21:04:01 +08:00
|
|
|
|
{/* 自定义选项 */}
|
2022-06-15 19:25:55 +08:00
|
|
|
|
{source === 'custom' ? (
|
|
|
|
|
<div className="ae-OptionControl-wrapper">
|
|
|
|
|
{Array.isArray(options) && options.length ? (
|
|
|
|
|
<ul className="ae-OptionControl-content" ref={this.dragRef}>
|
|
|
|
|
{options.map((option, index) =>
|
|
|
|
|
this.renderOption({...option, index, multipleProps})
|
|
|
|
|
)}
|
|
|
|
|
</ul>
|
|
|
|
|
) : (
|
|
|
|
|
<div className="ae-OptionControl-placeholder">无选项</div>
|
|
|
|
|
)}
|
|
|
|
|
<div className="ae-OptionControl-footer">
|
|
|
|
|
<Button
|
|
|
|
|
level="enhance"
|
|
|
|
|
onClick={this.handleAdd}
|
|
|
|
|
ref={this.targetRef}
|
|
|
|
|
>
|
|
|
|
|
添加选项
|
|
|
|
|
</Button>
|
|
|
|
|
{/* {render('option-control-batchAdd', this.buildBatchAddSchema())} */}
|
2023-04-11 15:11:55 +08:00
|
|
|
|
{render('inner', this.buildBatchAddSchema())}
|
2022-06-15 19:25:55 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* {this.renderPopover()} */}
|
|
|
|
|
</div>
|
|
|
|
|
) : null}
|
|
|
|
|
|
2023-09-11 21:04:01 +08:00
|
|
|
|
{/* API 接口 */}
|
2023-05-05 14:01:27 +08:00
|
|
|
|
{source === 'api' || source === 'apicenter'
|
|
|
|
|
? this.renderApiPanel()
|
|
|
|
|
: null}
|
|
|
|
|
|
2023-09-11 21:04:01 +08:00
|
|
|
|
{/* 上下文变量 */}
|
2023-05-05 14:01:27 +08:00
|
|
|
|
{source === 'variable'
|
2023-09-11 21:04:01 +08:00
|
|
|
|
? render('variable', {
|
|
|
|
|
type: 'control',
|
|
|
|
|
label: false,
|
|
|
|
|
className: 'ae-ExtendMore',
|
|
|
|
|
body: [
|
|
|
|
|
getSchemaTpl('sourceBindControl', {
|
|
|
|
|
label: false,
|
2023-10-09 11:03:19 +08:00
|
|
|
|
onChange: debounce(this.handleAPIChange, 1000)
|
2023-09-11 21:04:01 +08:00
|
|
|
|
})
|
|
|
|
|
].concat(this.getFuncFieldSchema())
|
|
|
|
|
})
|
2023-05-05 14:01:27 +08:00
|
|
|
|
: null}
|
2022-06-15 19:25:55 +08:00
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@FormItem({
|
|
|
|
|
type: 'ae-optionControl',
|
|
|
|
|
renderLabel: false
|
|
|
|
|
})
|
|
|
|
|
export class OptionControlRenderer extends OptionControl {}
|