enhance settings

This commit is contained in:
kangwei 2020-03-08 14:03:23 +08:00
parent 14ba3b02a2
commit 72a7d83f02
15 changed files with 169 additions and 45 deletions

View File

@ -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();

View File

@ -158,6 +158,10 @@ export class ComponentType {
name: 'size',
title: '大小',
setter: 'StringSetter'
}, {
name: 'age',
title: '年龄',
setter: 'NumberSetter'
}]
}, {
name: '#styles',

View File

@ -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';

View File

@ -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;

View File

@ -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"
]
}

View File

@ -0,0 +1,3 @@
import { NumberPicker } from '@alifd/next';
export default NumberPicker;

View File

@ -0,0 +1,5 @@
{
"dependencies": {
"@alifd/next": "^1.19.16"
}
}

View File

@ -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();

View File

@ -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.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) {
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;
}

View 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);
}

View File

@ -0,0 +1,5 @@
{
"dependencies": {
"@babel/runtime": "^7.8.7"
}
}

View 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;
}