support mixed setter

This commit is contained in:
kangwei 2020-04-21 18:07:56 +08:00
parent 27db010b8a
commit a15bfd8517
12 changed files with 223 additions and 135 deletions

View File

@ -15,6 +15,7 @@ export type RegisteredSetter = {
* for MixedSetter to manual change to this setter
*/
initialValue?: any | ((field: any) => any);
recommend?: boolean;
};
const settersMap = new Map<string, RegisteredSetter & {
type: string;

View File

@ -53,7 +53,7 @@ export class Field extends Component<FieldProps> {
check();
observer.observe(body, {
childList: true,
subtree: false,
subtree: true,
attributes: true,
attributeFilter: ['class'],
});

View File

@ -80,9 +80,14 @@
}
}
.lc-setter-actions {
display: flex;
align-items: center;
}
&.lc-block-field {
position: relative;
>.lc-field-body>.lc-block-setter>.lc-block-setter-actions {
>.lc-field-body>.lc-block-setter>.lc-setter-actions {
position: absolute;
right: 10px;
top: 0;

View File

@ -0,0 +1,16 @@
import { SVGIcon, IconProps } from "@ali/lowcode-globals";
export function IconConvert(props: IconProps) {
return (
<SVGIcon viewBox="0 0 1024 1024" {...props}>
<path d="M508.16 889.6C291.84 889.6 115.2 714.24 115.2 497.92 115.2 281.6 291.84 106.24 509.44 106.24c43.52 0 85.76 6.4 124.16 20.48l-10.24 30.72c-35.84-11.52-72.96-17.92-113.92-17.92-199.68 0-362.24 161.28-362.24 359.68s162.56 358.4 360.96 358.4 359.68-161.28 359.68-359.68c0-66.56-17.92-131.84-51.2-185.6L844.8 294.4c37.12 60.16 56.32 130.56 56.32 203.52-1.28 216.32-176.64 391.68-392.96 391.68z" />
<path d="M627.2 140.8m-15.36 0a15.36 15.36 0 1 0 30.72 0 15.36 15.36 0 1 0-30.72 0Z" />
<path d="M832 304.64m-15.36 0a15.36 15.36 0 1 0 30.72 0 15.36 15.36 0 1 0-30.72 0Z" />
<path d="M348.16 497.92m-35.84 0a35.84 35.84 0 1 0 71.68 0 35.84 35.84 0 1 0-71.68 0Z" fill="#35A2D4" />
<path d="M508.16 497.92m-35.84 0a35.84 35.84 0 1 0 71.68 0 35.84 35.84 0 1 0-71.68 0Z" fill="#35A2D4" />
<path d="M668.16 497.92m-35.84 0a35.84 35.84 0 1 0 71.68 0 35.84 35.84 0 1 0-71.68 0Z" fill="#35A2D4" />
</SVGIcon>
);
}
IconConvert.displayName = 'Convert';

View File

@ -1,10 +1,29 @@
import React, { PureComponent, Component } from 'react';
import React, { Component, isValidElement } from 'react';
import classNames from 'classnames';
import { Dropdown, Button, Menu, Icon } from '@alifd/next';
import { getSetter, getSettersMap, SetterConfig, computed, obx, CustomView, DynamicProps, DynamicSetter, TitleContent, isSetterConfig, Title, createSetterContent } from '@ali/lowcode-globals';
import { SettingField } from 'plugin-settings-pane/src/settings/main';
import { Dropdown, Button, Menu } from '@alifd/next';
import {
getSetter,
getSettersMap,
SetterConfig,
computed,
obx,
CustomView,
DynamicProps,
DynamicSetter,
TitleContent,
isSetterConfig,
Title,
createSetterContent,
observer,
isDynamicSetter,
shallowIntl,
EmbedTip,
isI18nData,
} from '@ali/lowcode-globals';
import { SettingField } from '../../settings/setting-field';
import { IconConvert } from '../../icons/convert';
import './index.scss';
import './style.less';
export interface SetterItem {
name: string;
@ -12,7 +31,8 @@ export interface SetterItem {
setter: string | DynamicSetter | CustomView;
props?: object | DynamicProps;
condition?: (field: SettingField) => boolean;
initialValue?: (field: SettingField) => any;
initialValue?: any | ((field: SettingField) => any);
list: boolean;
}
function nomalizeSetters(setters?: Array<string | SetterConfig | CustomView | DynamicSetter>): SetterItem[] {
@ -28,6 +48,7 @@ function nomalizeSetters(setters?: Array<string | SetterConfig | CustomView | Dy
setter: name,
condition: setter.condition,
initialValue: setter.initialValue,
list: setter.recommend || false,
});
});
return normalized;
@ -42,9 +63,10 @@ function nomalizeSetters(setters?: Array<string | SetterConfig | CustomView | Dy
names.push(got);
return got;
}
return setters.map(setter => {
return setters.map((setter) => {
const config: any = {
setter,
list: true,
};
if (isSetterConfig(setter)) {
config.setter = setter.componentName;
@ -76,149 +98,158 @@ function nomalizeSetters(setters?: Array<string | SetterConfig | CustomView | Dy
});
}
@observer
export default class MixedSetter extends Component<{
field: SettingField;
setters?: Array<string | SetterConfig | CustomView | DynamicSetter>;
onSetterChange?: (field: SettingField, name: string) => void;
onChange?: (val: any) => void;
value?: any;
className?: string;
}> {
private setters = nomalizeSetters(this.props.setters);
@obx.ref private used?: string;
@computed private getCurrentSetter() {
const { field } = this.props;
if (this.used != null) {
const selected = this.used;
if (selected.condition) {
if (selected.condition(field)) {
return selected;
let firstMatched: SetterItem | undefined;
for (const setter of this.setters) {
const matched = !setter.condition || setter.condition(field);
if (matched) {
if (setter.name === this.used) {
return setter;
}
} else {
return selected;
if (!firstMatched) {
firstMatched = setter;
}
}
return this.setters.find(item => {
if (!item.condition) {
return true;
}
return item.condition(field);
});
return firstMatched;
}
private useSetter: (id: string) => {
private useSetter = (name: string) => {
if (name === this.used) {
return;
}
const { field, onChange } = this.props;
const newValue = setter.initialValue?.(field);
this.used = setter;
const setter = this.setters.find((item) => item.name === name);
this.used = name;
if (setter) {
let newValue: any = setter.initialValue;
if (newValue && typeof newValue === 'function') {
newValue = newValue(field);
}
onChange && onChange(newValue);
}
render() {
const {
style = {},
className,
types = [],
defaultType,
...restProps
} = this.props;
this.typeMap = {};
let realTypes: any[] = [];
types.forEach( (el: { name: any; props: any; }) => {
const { name, props } = el;
const Setter = getSetter(name);
if (Setter) {
this.typeMap[name] = {
label: name,
component: Setter.component,
props,
}
}
realTypes.push(name);
})
let moreBtnNode = null;
//如果只有2种且有变量表达式则直接展示变量按钮
if (realTypes.length > 1) {
let isTwoType = !!(realTypes.length === 2 && ~realTypes.indexOf('ExpressionSetter'));
let btnProps = {
size: 'small',
text: true,
style: {
position: 'absolute',
left: '100%',
top: 0,
bottom: 0,
margin: 'auto 0 auto 8px',
padding: 0,
width: 16,
height: 16,
lineHeight: '16px',
textAlign: 'center'
}
};
if (isTwoType) {
btnProps.onClick = this.changeType.bind(this, realTypes.indexOf(this.state.type) ? realTypes[0] : realTypes[1]);
private shell: HTMLDivElement | null = null;
private checkIsBlockField() {
if (this.shell) {
const setter = this.shell.firstElementChild;
if (setter && setter.classList.contains('lc-block-setter')) {
this.shell.classList.add('lc-block-setter');
} else {
this.shell.classList.remove('lc-block-setter');
}
}
}
componentDidUpdate() {
this.checkIsBlockField();
}
componentDidMount() {
this.checkIsBlockField();
}
render() {
const { className, field, setters, onSetterChange, ...restProps } = this.props;
const currentSetter = this.getCurrentSetter();
const isTwoType = this.setters.length < 3;
let setterContent: any;
const triggerTitle: any = {
tip: {
type: 'i18n',
'zh-CN': '切换格式',
'en-US': 'Switch Format',
},
icon: <IconConvert size={24} />,
};
if (currentSetter) {
const { setter, title, props } = currentSetter;
let setterProps: any = {};
let setterType: any;
if (isDynamicSetter(setter)) {
setterType = setter(field);
} else {
setterType = setter;
}
if (props) {
setterProps = props;
if (typeof setterProps === 'function') {
setterProps = setterProps(field);
}
}
setterContent = createSetterContent(setterType, {
...shallowIntl(setterProps),
field,
...restProps,
});
if (title) {
if (typeof title !== 'object' || isI18nData(title) || isValidElement(title)) {
triggerTitle.tip = title;
} else {
triggerTitle.tip = title.tip || title.label;
}
}
} else {
// 未匹配的 null 值,显示 NullValue 空值
// 未匹配的 其它 值,显示 InvalidValue 非法值
let triggerNode = (
<Button {...btnProps} size={isTwoType ? 'large' : 'small'}>
<Icon type={isTwoType ? 'edit' : 'ellipsis'} />
</Button>
);
if (isTwoType) {
moreBtnNode = triggerNode;
if (restProps.value == null) {
setterContent = <span>NullValue</span>;
} else {
let MenuItems: {} | null | undefined = [];
realTypes.map(type => {
if (this.typeMap[type]) {
MenuItems.push(<Menu.Item key={type}></Menu.Item>);
} else {
console.error(
this.i18n('typeError', {
type
})
);
setterContent = <span>InvalidValue</span>;
}
});
let MenuNode = (
<Menu
selectMode="single"
hasSelectedIcon={false}
selectedKeys={this.used}
onItemClick={this.useSetter}
>
{this.setters.map((setter) => {
return <Menu.Item key={setter.name}>
<Title title={setter.title} />
</Menu.Item>
})}
</Menu>
}
const usedName = currentSetter?.name || this.used;
let moreBtnNode = (
<Title
title={triggerTitle}
onClick={
isTwoType
? () => {
if (this.setters[0]?.name === usedName) {
this.useSetter(this.setters[1]?.name);
} else {
this.useSetter(this.setters[0]?.name);
}
}
: undefined
}
/>
);
if (!isTwoType) {
moreBtnNode = (
<Dropdown trigger={triggerNode} triggerType="click">
<Menu
selectMode="single"
hasSelectedIcon={false}
selectedKeys={this.used}
onItemClick={this.useSetter}
>
{this.setters.map((setter) => {
return <Menu.Item key={setter.name}>
<Dropdown trigger={moreBtnNode} triggerType="click" align="tr br">
<Menu selectMode="single" hasSelectedIcon={true} selectedKeys={usedName} onItemClick={this.useSetter}>
{this.setters.filter(setter => setter.list || setter.name === usedName).map((setter) => {
return (
<Menu.Item key={setter.name}>
<Title title={setter.title} />
</Menu.Item>
);
})}
</Menu>
</Dropdown>
);
}
}
let TargetNode = this.typeMap[this.state.type]?.component || 'div';
let targetProps = this.typeMap[this.state.type]?.props || {};
let tarStyle = { position: 'relative', ...style };
let classes = classNames(className, 'lowcode-setter-mixin');
return (
<div style={tarStyle} className={classes} >
{createSetterContent()}
{moreBtnNode}
<div ref={(shell) => (this.shell = shell)} className={classNames('lc-setter-mixed', className)}>
{setterContent}
<div className="lc-setter-actions">{moreBtnNode}</div>
</div>
);
}

View File

@ -0,0 +1,11 @@
.lc-setter-mixed {
display: flex;
align-items: center;
width: 100%;
.lc-setter-actions {
margin-left: 5px;
}
&.lc-block-setter {
display: block;
}
}

View File

@ -1,6 +1,29 @@
import { registerSetter } from '@ali/lowcode-globals';
import { registerSetter, isPlainObject } from '@ali/lowcode-globals';
import ArraySetter from './array-setter';
import ObjectSetter from './object-setter';
import MixedSetter from './mixed-setter';
registerSetter('ArraySetter', ArraySetter);
registerSetter('ObjectSetter', ObjectSetter);
registerSetter('ArraySetter', {
component: ArraySetter,
defaultProps: {},
title: 'ArraySetter', // TODO
condition: (field: any) => {
const v = field.getValue();
return v == null || Array.isArray(v);
},
initialValue: [],
recommend: true,
});
registerSetter('ObjectSetter', {
component: ObjectSetter,
// todo: defaultProps
defaultProps: {},
title: 'ObjectSetter', // TODO
condition: (field: any) => {
const v = field.getValue();
return v == null || isPlainObject(v);
},
initialValue: {},
recommend: true,
});
registerSetter('MixedSetter', MixedSetter);

View File

@ -404,11 +404,10 @@ export class SettingPropEntry implements SettingTarget {
return this.name;
}
/*
getDefaultValue() {
return this.extraProps.defaultValue;
}
/*
getConfig<K extends keyof IPropConfig>(configName?: K): IPropConfig[K] | IPropConfig {
if (configName) {
return this.config[configName];

View File

@ -38,8 +38,6 @@ export class SettingField extends SettingPropEntry implements SettingTarget {
constructor(readonly parent: SettingTarget, config: FieldConfig) {
super(parent, config.name, config.type);
console.info(config);
const { title, items, setter, extraProps, ...rest } = config;
this._title = title;
this._setter = setter;

View File

@ -25,6 +25,7 @@ class SettingFieldView extends Component<{ field: SettingField }> {
return null;
}
const { setter } = field;
let setterProps: any = {};
let setterType: any;
if (Array.isArray(setter)) {

View File

@ -370,12 +370,14 @@ export function upgradePropConfig(config: OldPropConfig) {
const setters = Array.isArray(primarySetter) ? primarySetter.concat('ExpressionSetter') : [primarySetter, 'ExpressionSetter'];
primarySetter = {
componentName: 'MixedSetter',
props: {
setters,
onSetterChange: (field: Field, name: string) => {
if (useVariableChange) {
useVariableChange.call(field, { isUseVariable: name === 'ExpressionSetter' });
}
}
}
};
} else {
primarySetter = 'ExpressionSetter';

View File

@ -7,6 +7,7 @@ const { editor } = Engine;
Engine.init();
load();
Engine.Env.setEnv('RE_VERSION', "5.0.1");
async function load() {
await loadAssets();