mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-14 13:03:07 +00:00
enhance settings
This commit is contained in:
parent
14ba3b02a2
commit
72a7d83f02
@ -1,5 +1,6 @@
|
|||||||
import { ViewController } from '@ali/recore';
|
import { ViewController } from '@ali/recore';
|
||||||
import DesignView from '../../designer/src';
|
import DesignView from '../../designer/src';
|
||||||
|
import NumberSetter from '../../plugin-setters/number-setter';
|
||||||
import SettingsPane, { registerSetter } from '../../plugin-settings-pane/src';
|
import SettingsPane, { registerSetter } from '../../plugin-settings-pane/src';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { Input } from '@alifd/next';
|
import { Input } from '@alifd/next';
|
||||||
@ -17,7 +18,9 @@ registerSetter('EventsSetter', () => {
|
|||||||
}, '这里是事件设置');
|
}, '这里是事件设置');
|
||||||
});
|
});
|
||||||
|
|
||||||
registerSetter('StringSetter', Input);
|
registerSetter('StringSetter', { component: Input, props: { placeholder: "请输入" } });
|
||||||
|
|
||||||
|
registerSetter('NumberSetter', NumberSetter as any);
|
||||||
|
|
||||||
const emitter = new EventEmitter();
|
const emitter = new EventEmitter();
|
||||||
|
|
||||||
|
|||||||
@ -158,6 +158,10 @@ export class ComponentType {
|
|||||||
name: 'size',
|
name: 'size',
|
||||||
title: '大小',
|
title: '大小',
|
||||||
setter: 'StringSetter'
|
setter: 'StringSetter'
|
||||||
|
}, {
|
||||||
|
name: 'age',
|
||||||
|
title: '年龄',
|
||||||
|
setter: 'NumberSetter'
|
||||||
}]
|
}]
|
||||||
}, {
|
}, {
|
||||||
name: '#styles',
|
name: '#styles',
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { valueToSource } from '../../../../utils/value-to-source';
|
|||||||
import { CompositeValue, isJSExpression, isJSSlot, NodeSchema, NodeData, isNodeSchema } from '../../../schema';
|
import { CompositeValue, isJSExpression, isJSSlot, NodeSchema, NodeData, isNodeSchema } from '../../../schema';
|
||||||
import PropStash from './prop-stash';
|
import PropStash from './prop-stash';
|
||||||
import { uniqueId } from '../../../../../../utils/unique-id';
|
import { uniqueId } from '../../../../../../utils/unique-id';
|
||||||
import { isPlainObject } from '../../../../utils/is-plain-object';
|
import { isPlainObject } from '../../../../../../utils/is-plain-object';
|
||||||
import { hasOwnProperty } from '../../../../utils/has-own-property';
|
import { hasOwnProperty } from '../../../../utils/has-own-property';
|
||||||
import Props from './props';
|
import Props from './props';
|
||||||
import Node from '../node';
|
import Node from '../node';
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { isPlainObject } from './is-plain-object';
|
import { isPlainObject } from '../../../utils/is-plain-object';
|
||||||
|
|
||||||
export function cloneDeep(src: any): any {
|
export function cloneDeep(src: any): any {
|
||||||
const type = typeof src;
|
const type = typeof src;
|
||||||
|
|||||||
@ -4,6 +4,6 @@
|
|||||||
"experimentalDecorators": true
|
"experimentalDecorators": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"./src/", "../utils/unique-id.ts"
|
"./src/", "../utils/unique-id.ts", "../utils/is-plain-object.ts", "../utils/is-object.ts", "../utils/is-function.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
3
packages/plugin-setters/number-setter.tsx
Normal file
3
packages/plugin-setters/number-setter.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { NumberPicker } from '@alifd/next';
|
||||||
|
|
||||||
|
export default NumberPicker;
|
||||||
5
packages/plugin-setters/package.json
Normal file
5
packages/plugin-setters/package.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@alifd/next": "^1.19.16"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -74,6 +74,8 @@ export function isCustomView(obj: any): obj is CustomView {
|
|||||||
return obj && (isValidElement(obj) || isReactComponent(obj));
|
return obj && (isValidElement(obj) || isReactComponent(obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DynamicProps = (field: SettingField, editor: any) => object;
|
||||||
|
|
||||||
export interface SetterConfig {
|
export interface SetterConfig {
|
||||||
/**
|
/**
|
||||||
* if *string* passed must be a registered Setter Name
|
* if *string* passed must be a registered Setter Name
|
||||||
@ -82,9 +84,7 @@ export interface SetterConfig {
|
|||||||
/**
|
/**
|
||||||
* the props pass to Setter Component
|
* the props pass to Setter Component
|
||||||
*/
|
*/
|
||||||
props?: {
|
props?: object | DynamicProps;
|
||||||
[prop: string]: any;
|
|
||||||
};
|
|
||||||
children?: any;
|
children?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,26 +225,57 @@ export class SettingField implements SettingTarget {
|
|||||||
|
|
||||||
// ====== 当前属性读写 =====
|
// ====== 当前属性读写 =====
|
||||||
|
|
||||||
|
// Todo cache!!
|
||||||
|
/**
|
||||||
|
* 判断当前属性值是否一致
|
||||||
|
*/
|
||||||
get isSameValue(): boolean {
|
get isSameValue(): boolean {
|
||||||
// todo:
|
if (this.type !== 'field') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const propName = this.path.join('.');
|
||||||
|
const first = this.nodes[0].getProp(propName)!;
|
||||||
|
let l = this.nodes.length;
|
||||||
|
while (l-- > 1) {
|
||||||
|
const next = this.nodes[l].getProp(propName, false);
|
||||||
|
if (!first.isEqual(next)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前属性值
|
||||||
|
*/
|
||||||
getValue(): any {
|
getValue(): any {
|
||||||
|
if (this.type !== 'field') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return this.parent.getPropValue(this.name);
|
return this.parent.getPropValue(this.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置当前属性值
|
||||||
|
*/
|
||||||
setValue(val: any) {
|
setValue(val: any) {
|
||||||
|
if (this.type !== 'field') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.parent.setPropValue(this.name, val);
|
this.parent.setPropValue(this.name, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置属性值
|
/**
|
||||||
|
* 设置子级属性值
|
||||||
|
*/
|
||||||
setPropValue(propName: string, value: any) {
|
setPropValue(propName: string, value: any) {
|
||||||
const path = this.type === 'field' ? `${this.name}.${propName}` : propName;
|
const path = this.type === 'field' ? `${this.name}.${propName}` : propName;
|
||||||
this.parent.setPropValue(path, value);
|
this.parent.setPropValue(path, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取属性值
|
/**
|
||||||
|
* 获取子级属性值
|
||||||
|
*/
|
||||||
getPropValue(propName: string): any {
|
getPropValue(propName: string): any {
|
||||||
const path = this.type === 'field' ? `${this.name}.${propName}` : propName;
|
const path = this.type === 'field' ? `${this.name}.${propName}` : propName;
|
||||||
return this.parent.getPropValue(path);
|
return this.parent.getPropValue(path);
|
||||||
@ -418,7 +449,7 @@ export class SettingsMain implements SettingTarget {
|
|||||||
// setups
|
// setups
|
||||||
this.setupComponentType();
|
this.setupComponentType();
|
||||||
|
|
||||||
// todo: enhance that componentType not changed
|
// todo: enhance when componentType not changed do merge
|
||||||
// clear fields
|
// clear fields
|
||||||
this.setupItems();
|
this.setupItems();
|
||||||
|
|
||||||
|
|||||||
@ -1,42 +1,96 @@
|
|||||||
import { Component, isValidElement, ReactNode, ReactElement, ComponentType as ReactComponentType } from 'react';
|
import { Component, ReactNode } from 'react';
|
||||||
import { isReactClass } from '../../utils/is-react';
|
|
||||||
import { createContent } from '../../utils/create-content';
|
import { createContent } from '../../utils/create-content';
|
||||||
import { SettingField, CustomView, isSettingField, SettingTarget } from './main';
|
import { shallowEqual } from '../../utils/shallow-equal';
|
||||||
|
import {
|
||||||
|
SettingField,
|
||||||
|
CustomView,
|
||||||
|
isSettingField,
|
||||||
|
SettingTarget,
|
||||||
|
SetterConfig,
|
||||||
|
isCustomView,
|
||||||
|
DynamicProps,
|
||||||
|
} from './main';
|
||||||
import { Field, FieldGroup } from './field';
|
import { Field, FieldGroup } from './field';
|
||||||
|
|
||||||
const settersMap = new Map<string, ReactElement | ReactComponentType<any>>();
|
export type RegisteredSetter = CustomView | {
|
||||||
export function registerSetter(type: string, setter: ReactElement | ReactComponentType<any>) {
|
component: CustomView;
|
||||||
|
props?: object;
|
||||||
|
};
|
||||||
|
|
||||||
|
const settersMap = new Map<string, RegisteredSetter>();
|
||||||
|
export function registerSetter(type: string, setter: RegisteredSetter) {
|
||||||
settersMap.set(type, setter);
|
settersMap.set(type, setter);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSetter(type: string): ReactElement | ReactComponentType<any> | null {
|
export function getSetter(type: string): RegisteredSetter | null {
|
||||||
return settersMap.get(type) || null;
|
return settersMap.get(type) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createSetterContent(setter: any, props: object): ReactNode {
|
export function createSetterContent(setter: any, props: object): ReactNode {
|
||||||
if (typeof setter === 'string') {
|
if (typeof setter === 'string') {
|
||||||
setter = getSetter(setter);
|
setter = getSetter(setter);
|
||||||
|
if (!isCustomView(setter)) {
|
||||||
|
if (setter.props) {
|
||||||
|
props = {
|
||||||
|
...setter.props,
|
||||||
|
...props,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
setter = setter.component;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return createContent(setter, props);
|
return createContent(setter, props);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isSetterConfig(obj: any): obj is SetterConfig {
|
||||||
|
return obj && typeof obj === 'object' && 'componentName' in obj && !isCustomView(obj);
|
||||||
|
}
|
||||||
|
|
||||||
class SettingFieldView extends Component<{ field: SettingField }> {
|
class SettingFieldView extends Component<{ field: SettingField }> {
|
||||||
state = {
|
state = {
|
||||||
visible: false,
|
visible: false,
|
||||||
value: null,
|
value: null,
|
||||||
|
setterProps: {},
|
||||||
};
|
};
|
||||||
private dispose: () => void;
|
private dispose: () => void;
|
||||||
|
private setterType?: string | CustomView;
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
const { field } = this.props;
|
const { field } = this.props;
|
||||||
const { condition } = field.extraProps;
|
const { setter } = field;
|
||||||
|
let setterProps: object | DynamicProps = {};
|
||||||
|
if (isSetterConfig(setter)) {
|
||||||
|
this.setterType = setter.componentName;
|
||||||
|
if (setter.props) {
|
||||||
|
setterProps = setter.props;
|
||||||
|
}
|
||||||
|
} else if (setter) {
|
||||||
|
this.setterType = setter;
|
||||||
|
}
|
||||||
let firstRun: boolean = true;
|
let firstRun: boolean = true;
|
||||||
this.dispose = field.onEffect(() => {
|
this.dispose = field.onEffect(() => {
|
||||||
const state: any = {};
|
const state: any = {};
|
||||||
state.visible = (field.isOne && typeof condition === 'function') ? !condition(field, field.editor) : true;
|
const { extraProps, editor } = field;
|
||||||
|
const { condition, defaultValue } = extraProps;
|
||||||
|
state.visible = field.isOne && typeof condition === 'function' ? !condition(field, editor) : true;
|
||||||
if (state.visible) {
|
if (state.visible) {
|
||||||
state.value = field.getValue();
|
state.setterProps = {
|
||||||
|
...(typeof setterProps === 'function' ? setterProps(field, editor) : setterProps),
|
||||||
|
};
|
||||||
|
if (field.type === 'field') {
|
||||||
|
state.value = field.getValue();
|
||||||
|
if (defaultValue != null && !('defaultValue' in state.setterProps)) {
|
||||||
|
state.setterProps.defaultValue = defaultValue;
|
||||||
|
}
|
||||||
|
if (!field.isSameValue) {
|
||||||
|
state.setterProps.multiValue = true;
|
||||||
|
if (!('placeholder' in props)) {
|
||||||
|
state.setterProps.placeholder = '多种值';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: error handling
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (firstRun) {
|
if (firstRun) {
|
||||||
firstRun = false;
|
firstRun = false;
|
||||||
@ -48,7 +102,12 @@ class SettingFieldView extends Component<{ field: SettingField }> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(_: any, nextState: any) {
|
shouldComponentUpdate(_: any, nextState: any) {
|
||||||
if (nextState.value !== this.state.value || nextState.visible !== this.state.visible) {
|
const { state } = this;
|
||||||
|
if (
|
||||||
|
nextState.value !== state.value ||
|
||||||
|
nextState.visible !== state.visible ||
|
||||||
|
!shallowEqual(state.setterProps, nextState.setterProps)
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -60,35 +119,17 @@ class SettingFieldView extends Component<{ field: SettingField }> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { field } = this.props;
|
const { field } = this.props;
|
||||||
const { setter, title, extraProps } = field;
|
const { visible, value, setterProps } = this.state;
|
||||||
const { defaultValue } = extraProps;
|
|
||||||
const { visible, value } = this.state;
|
|
||||||
// reaction point
|
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let setterType = setter;
|
|
||||||
let props: any = {};
|
|
||||||
if (typeof setter === 'object' && 'componentName' in setter && !(isValidElement(setter) || isReactClass(setter))) {
|
|
||||||
setterType = (setter as any).componentName;
|
|
||||||
props = (setter as any).props;
|
|
||||||
}
|
|
||||||
if (defaultValue != null && !('defaultValue' in props)) {
|
|
||||||
props.defaultValue = defaultValue;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
if (!('placeholder' in props) && !isSameValue) {
|
|
||||||
props.placeholder = '多种值';
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// todo: error handling
|
// todo: error handling
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Field title={title}>
|
<Field title={field.title}>
|
||||||
{createSetterContent(setterType, {
|
{createSetterContent(this.setterType, {
|
||||||
...props,
|
...setterProps,
|
||||||
key: field.id,
|
key: field.id,
|
||||||
// === injection
|
// === injection
|
||||||
prop: field,
|
prop: field,
|
||||||
@ -100,7 +141,7 @@ class SettingFieldView extends Component<{ field: SettingField }> {
|
|||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
field.setValue(value);
|
field.setValue(value);
|
||||||
}
|
},
|
||||||
})}
|
})}
|
||||||
</Field>
|
</Field>
|
||||||
);
|
);
|
||||||
@ -120,7 +161,7 @@ class SettingGroupView extends Component<{ field: SettingField }> {
|
|||||||
let firstRun: boolean = true;
|
let firstRun: boolean = true;
|
||||||
this.dispose = field.onEffect(() => {
|
this.dispose = field.onEffect(() => {
|
||||||
const state: any = {};
|
const state: any = {};
|
||||||
state.visible = (field.isOne && typeof condition === 'function') ? !condition(field, field.editor) : true;
|
state.visible = field.isOne && typeof condition === 'function' ? !condition(field, field.editor) : true;
|
||||||
if (state.visible) {
|
if (state.visible) {
|
||||||
state.items = field.items.slice();
|
state.items = field.items.slice();
|
||||||
}
|
}
|
||||||
@ -134,6 +175,7 @@ class SettingGroupView extends Component<{ field: SettingField }> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(_: any, nextState: any) {
|
shouldComponentUpdate(_: any, nextState: any) {
|
||||||
|
// todo: shallowEqual ?
|
||||||
if (nextState.items !== this.state.items || nextState.visible !== this.state.visible) {
|
if (nextState.items !== this.state.items || nextState.visible !== this.state.visible) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
4
packages/utils/has-own-property.ts
Normal file
4
packages/utils/has-own-property.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
const prototypeHasOwnProperty = Object.prototype.hasOwnProperty;
|
||||||
|
export function hasOwnProperty(obj: any, key: string | number | symbol): boolean {
|
||||||
|
return obj && prototypeHasOwnProperty.call(obj, key);
|
||||||
|
}
|
||||||
5
packages/utils/package.json
Normal file
5
packages/utils/package.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.8.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
27
packages/utils/shallow-equal.ts
Normal file
27
packages/utils/shallow-equal.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { hasOwnProperty } from './has-own-property';
|
||||||
|
|
||||||
|
export function shallowEqual(objA: any, objB: any): boolean {
|
||||||
|
if (objA === objB) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keysA = Object.keys(objA);
|
||||||
|
const keysB = Object.keys(objB);
|
||||||
|
|
||||||
|
if (keysA.length !== keysB.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test for A's keys different from B.
|
||||||
|
for (let i = 0; i < keysA.length; i++) {
|
||||||
|
if (!hasOwnProperty(objB, keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user