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

View File

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

View File

@ -80,9 +80,14 @@
} }
} }
.lc-setter-actions {
display: flex;
align-items: center;
}
&.lc-block-field { &.lc-block-field {
position: relative; position: relative;
>.lc-field-body>.lc-block-setter>.lc-block-setter-actions { >.lc-field-body>.lc-block-setter>.lc-setter-actions {
position: absolute; position: absolute;
right: 10px; right: 10px;
top: 0; 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 classNames from 'classnames';
import { Dropdown, Button, Menu, Icon } from '@alifd/next'; import { Dropdown, Button, Menu } from '@alifd/next';
import { getSetter, getSettersMap, SetterConfig, computed, obx, CustomView, DynamicProps, DynamicSetter, TitleContent, isSetterConfig, Title, createSetterContent } from '@ali/lowcode-globals'; import {
import { SettingField } from 'plugin-settings-pane/src/settings/main'; 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 { export interface SetterItem {
name: string; name: string;
@ -12,7 +31,8 @@ export interface SetterItem {
setter: string | DynamicSetter | CustomView; setter: string | DynamicSetter | CustomView;
props?: object | DynamicProps; props?: object | DynamicProps;
condition?: (field: SettingField) => boolean; condition?: (field: SettingField) => boolean;
initialValue?: (field: SettingField) => any; initialValue?: any | ((field: SettingField) => any);
list: boolean;
} }
function nomalizeSetters(setters?: Array<string | SetterConfig | CustomView | DynamicSetter>): SetterItem[] { function nomalizeSetters(setters?: Array<string | SetterConfig | CustomView | DynamicSetter>): SetterItem[] {
@ -28,6 +48,7 @@ function nomalizeSetters(setters?: Array<string | SetterConfig | CustomView | Dy
setter: name, setter: name,
condition: setter.condition, condition: setter.condition,
initialValue: setter.initialValue, initialValue: setter.initialValue,
list: setter.recommend || false,
}); });
}); });
return normalized; return normalized;
@ -42,9 +63,10 @@ function nomalizeSetters(setters?: Array<string | SetterConfig | CustomView | Dy
names.push(got); names.push(got);
return got; return got;
} }
return setters.map(setter => { return setters.map((setter) => {
const config: any = { const config: any = {
setter, setter,
list: true,
}; };
if (isSetterConfig(setter)) { if (isSetterConfig(setter)) {
config.setter = setter.componentName; config.setter = setter.componentName;
@ -76,149 +98,158 @@ function nomalizeSetters(setters?: Array<string | SetterConfig | CustomView | Dy
}); });
} }
@observer
export default class MixedSetter extends Component<{ export default class MixedSetter extends Component<{
field: SettingField; field: SettingField;
setters?: Array<string | SetterConfig | CustomView | DynamicSetter>; setters?: Array<string | SetterConfig | CustomView | DynamicSetter>;
onSetterChange?: (field: SettingField, name: string) => void; onSetterChange?: (field: SettingField, name: string) => void;
onChange?: (val: any) => void;
value?: any;
className?: string;
}> { }> {
private setters = nomalizeSetters(this.props.setters); private setters = nomalizeSetters(this.props.setters);
@obx.ref private used?: string; @obx.ref private used?: string;
@computed private getCurrentSetter() { @computed private getCurrentSetter() {
const { field } = this.props; const { field } = this.props;
if (this.used != null) { let firstMatched: SetterItem | undefined;
const selected = this.used; for (const setter of this.setters) {
if (selected.condition) { const matched = !setter.condition || setter.condition(field);
if (selected.condition(field)) { if (matched) {
return selected; if (setter.name === this.used) {
return setter;
}
if (!firstMatched) {
firstMatched = setter;
} }
} else {
return selected;
} }
} }
return this.setters.find(item => { return firstMatched;
if (!item.condition) {
return true;
}
return item.condition(field);
});
} }
private useSetter = (name: string) => {
private useSetter: (id: string) => { if (name === this.used) {
return;
}
const { field, onChange } = this.props; const { field, onChange } = this.props;
const newValue = setter.initialValue?.(field); const setter = this.setters.find((item) => item.name === name);
this.used = setter; this.used = name;
onChange && onChange(newValue); if (setter) {
let newValue: any = setter.initialValue;
if (newValue && typeof newValue === 'function') {
newValue = newValue(field);
}
onChange && onChange(newValue);
}
};
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() { render() {
const { const { className, field, setters, onSetterChange, ...restProps } = this.props;
style = {},
className, const currentSetter = this.getCurrentSetter();
types = [], const isTwoType = this.setters.length < 3;
defaultType,
...restProps let setterContent: any;
} = this.props; const triggerTitle: any = {
this.typeMap = {}; tip: {
let realTypes: any[] = []; type: 'i18n',
types.forEach( (el: { name: any; props: any; }) => { 'zh-CN': '切换格式',
const { name, props } = el; 'en-US': 'Switch Format',
const Setter = getSetter(name); },
if (Setter) { icon: <IconConvert size={24} />,
this.typeMap[name] = { };
label: name, if (currentSetter) {
component: Setter.component, const { setter, title, props } = currentSetter;
props, 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);
} }
} }
realTypes.push(name);
}) setterContent = createSetterContent(setterType, {
let moreBtnNode = null; ...shallowIntl(setterProps),
//如果只有2种且有变量表达式则直接展示变量按钮 field,
if (realTypes.length > 1) { ...restProps,
let isTwoType = !!(realTypes.length === 2 && ~realTypes.indexOf('ExpressionSetter')); });
let btnProps = { if (title) {
size: 'small', if (typeof title !== 'object' || isI18nData(title) || isValidElement(title)) {
text: true, triggerTitle.tip = title;
style: { } else {
position: 'absolute', triggerTitle.tip = title.tip || title.label;
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]);
} }
} else {
// 未匹配的 null 值,显示 NullValue 空值 // 未匹配的 null 值,显示 NullValue 空值
// 未匹配的 其它 值,显示 InvalidValue 非法值 // 未匹配的 其它 值,显示 InvalidValue 非法值
let triggerNode = ( if (restProps.value == null) {
<Button {...btnProps} size={isTwoType ? 'large' : 'small'}> setterContent = <span>NullValue</span>;
<Icon type={isTwoType ? 'edit' : 'ellipsis'} />
</Button>
);
if (isTwoType) {
moreBtnNode = triggerNode;
} else { } else {
let MenuItems: {} | null | undefined = []; setterContent = <span>InvalidValue</span>;
realTypes.map(type => {
if (this.typeMap[type]) {
MenuItems.push(<Menu.Item key={type}></Menu.Item>);
} else {
console.error(
this.i18n('typeError', {
type
})
);
}
});
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>
);
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}>
<Title title={setter.title} />
</Menu.Item>
})}
</Menu>
</Dropdown>
);
} }
} }
let TargetNode = this.typeMap[this.state.type]?.component || 'div'; const usedName = currentSetter?.name || this.used;
let targetProps = this.typeMap[this.state.type]?.props || {}; let moreBtnNode = (
let tarStyle = { position: 'relative', ...style }; <Title
let classes = classNames(className, 'lowcode-setter-mixin'); 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={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>
);
}
return ( return (
<div style={tarStyle} className={classes} > <div ref={(shell) => (this.shell = shell)} className={classNames('lc-setter-mixed', className)}>
{createSetterContent()} {setterContent}
{moreBtnNode}
<div className="lc-setter-actions">{moreBtnNode}</div>
</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 ArraySetter from './array-setter';
import ObjectSetter from './object-setter'; import ObjectSetter from './object-setter';
import MixedSetter from './mixed-setter';
registerSetter('ArraySetter', ArraySetter); registerSetter('ArraySetter', {
registerSetter('ObjectSetter', ObjectSetter); 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; return this.name;
} }
/*
getDefaultValue() { getDefaultValue() {
return this.extraProps.defaultValue; return this.extraProps.defaultValue;
} }
/*
getConfig<K extends keyof IPropConfig>(configName?: K): IPropConfig[K] | IPropConfig { getConfig<K extends keyof IPropConfig>(configName?: K): IPropConfig[K] | IPropConfig {
if (configName) { if (configName) {
return this.config[configName]; return this.config[configName];

View File

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

View File

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

View File

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

View File

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