mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-14 21:12:53 +00:00
enhance settings
This commit is contained in:
parent
14ba3b02a2
commit
72a7d83f02
@ -1,5 +1,6 @@
|
||||
import { ViewController } from '@ali/recore';
|
||||
import DesignView from '../../designer/src';
|
||||
import NumberSetter from '../../plugin-setters/number-setter';
|
||||
import SettingsPane, { registerSetter } from '../../plugin-settings-pane/src';
|
||||
import { EventEmitter } from 'events';
|
||||
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();
|
||||
|
||||
|
||||
@ -158,6 +158,10 @@ export class ComponentType {
|
||||
name: 'size',
|
||||
title: '大小',
|
||||
setter: 'StringSetter'
|
||||
}, {
|
||||
name: 'age',
|
||||
title: '年龄',
|
||||
setter: 'NumberSetter'
|
||||
}]
|
||||
}, {
|
||||
name: '#styles',
|
||||
|
||||
@ -3,7 +3,7 @@ import { valueToSource } from '../../../../utils/value-to-source';
|
||||
import { CompositeValue, isJSExpression, isJSSlot, NodeSchema, NodeData, isNodeSchema } from '../../../schema';
|
||||
import PropStash from './prop-stash';
|
||||
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 Props from './props';
|
||||
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 {
|
||||
const type = typeof src;
|
||||
|
||||
@ -4,6 +4,6 @@
|
||||
"experimentalDecorators": true
|
||||
},
|
||||
"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));
|
||||
}
|
||||
|
||||
export type DynamicProps = (field: SettingField, editor: any) => object;
|
||||
|
||||
export interface SetterConfig {
|
||||
/**
|
||||
* if *string* passed must be a registered Setter Name
|
||||
@ -82,9 +84,7 @@ export interface SetterConfig {
|
||||
/**
|
||||
* the props pass to Setter Component
|
||||
*/
|
||||
props?: {
|
||||
[prop: string]: any;
|
||||
};
|
||||
props?: object | DynamicProps;
|
||||
children?: any;
|
||||
}
|
||||
|
||||
@ -225,26 +225,57 @@ export class SettingField implements SettingTarget {
|
||||
|
||||
// ====== 当前属性读写 =====
|
||||
|
||||
// Todo cache!!
|
||||
/**
|
||||
* 判断当前属性值是否一致
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前属性值
|
||||
*/
|
||||
getValue(): any {
|
||||
if (this.type !== 'field') {
|
||||
return null;
|
||||
}
|
||||
return this.parent.getPropValue(this.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前属性值
|
||||
*/
|
||||
setValue(val: any) {
|
||||
if (this.type !== 'field') {
|
||||
return;
|
||||
}
|
||||
this.parent.setPropValue(this.name, val);
|
||||
}
|
||||
|
||||
// 设置属性值
|
||||
/**
|
||||
* 设置子级属性值
|
||||
*/
|
||||
setPropValue(propName: string, value: any) {
|
||||
const path = this.type === 'field' ? `${this.name}.${propName}` : propName;
|
||||
this.parent.setPropValue(path, value);
|
||||
}
|
||||
|
||||
// 获取属性值
|
||||
/**
|
||||
* 获取子级属性值
|
||||
*/
|
||||
getPropValue(propName: string): any {
|
||||
const path = this.type === 'field' ? `${this.name}.${propName}` : propName;
|
||||
return this.parent.getPropValue(path);
|
||||
@ -418,7 +449,7 @@ export class SettingsMain implements SettingTarget {
|
||||
// setups
|
||||
this.setupComponentType();
|
||||
|
||||
// todo: enhance that componentType not changed
|
||||
// todo: enhance when componentType not changed do merge
|
||||
// clear fields
|
||||
this.setupItems();
|
||||
|
||||
|
||||
@ -1,42 +1,96 @@
|
||||
import { Component, isValidElement, ReactNode, ReactElement, ComponentType as ReactComponentType } from 'react';
|
||||
import { isReactClass } from '../../utils/is-react';
|
||||
import { Component, ReactNode } from 'react';
|
||||
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';
|
||||
|
||||
const settersMap = new Map<string, ReactElement | ReactComponentType<any>>();
|
||||
export function registerSetter(type: string, setter: ReactElement | ReactComponentType<any>) {
|
||||
export type RegisteredSetter = CustomView | {
|
||||
component: CustomView;
|
||||
props?: object;
|
||||
};
|
||||
|
||||
const settersMap = new Map<string, RegisteredSetter>();
|
||||
export function registerSetter(type: string, setter: RegisteredSetter) {
|
||||
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;
|
||||
}
|
||||
|
||||
export function createSetterContent(setter: any, props: object): ReactNode {
|
||||
if (typeof setter === 'string') {
|
||||
setter = getSetter(setter);
|
||||
if (!isCustomView(setter)) {
|
||||
if (setter.props) {
|
||||
props = {
|
||||
...setter.props,
|
||||
...props,
|
||||
};
|
||||
}
|
||||
setter = setter.component;
|
||||
}
|
||||
}
|
||||
|
||||
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 }> {
|
||||
state = {
|
||||
visible: false,
|
||||
value: null,
|
||||
setterProps: {},
|
||||
};
|
||||
private dispose: () => void;
|
||||
private setterType?: string | CustomView;
|
||||
constructor(props: any) {
|
||||
super(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;
|
||||
this.dispose = field.onEffect(() => {
|
||||
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) {
|
||||
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) {
|
||||
firstRun = false;
|
||||
@ -48,7 +102,12 @@ class SettingFieldView extends Component<{ field: SettingField }> {
|
||||
}
|
||||
|
||||
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 false;
|
||||
@ -60,35 +119,17 @@ class SettingFieldView extends Component<{ field: SettingField }> {
|
||||
|
||||
render() {
|
||||
const { field } = this.props;
|
||||
const { setter, title, extraProps } = field;
|
||||
const { defaultValue } = extraProps;
|
||||
const { visible, value } = this.state;
|
||||
// reaction point
|
||||
const { visible, value, setterProps } = this.state;
|
||||
if (!visible) {
|
||||
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
|
||||
|
||||
return (
|
||||
<Field title={title}>
|
||||
{createSetterContent(setterType, {
|
||||
...props,
|
||||
<Field title={field.title}>
|
||||
{createSetterContent(this.setterType, {
|
||||
...setterProps,
|
||||
key: field.id,
|
||||
// === injection
|
||||
prop: field,
|
||||
@ -100,7 +141,7 @@ class SettingFieldView extends Component<{ field: SettingField }> {
|
||||
value,
|
||||
});
|
||||
field.setValue(value);
|
||||
}
|
||||
},
|
||||
})}
|
||||
</Field>
|
||||
);
|
||||
@ -120,7 +161,7 @@ class SettingGroupView extends Component<{ field: SettingField }> {
|
||||
let firstRun: boolean = true;
|
||||
this.dispose = field.onEffect(() => {
|
||||
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) {
|
||||
state.items = field.items.slice();
|
||||
}
|
||||
@ -134,6 +175,7 @@ class SettingGroupView extends Component<{ field: SettingField }> {
|
||||
}
|
||||
|
||||
shouldComponentUpdate(_: any, nextState: any) {
|
||||
// todo: shallowEqual ?
|
||||
if (nextState.items !== this.state.items || nextState.visible !== this.state.visible) {
|
||||
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