refactor(props): 将底层 prop 的层级调整成跟元数据描述一致, 支持通过 API 方式设置 prop 分支节点

This commit is contained in:
力皓 2021-06-08 13:49:20 +08:00
parent 0cdd1060b7
commit 6176608ac7
5 changed files with 82 additions and 23 deletions

View File

@ -6,13 +6,13 @@ import { computed, obx } from '@ali/lowcode-editor-core';
import { cloneDeep } from '@ali/lowcode-utils';
function getSettingFieldCollectorKey(parent: SettingEntry, config: FieldConfig) {
let top = parent;
let cur = parent;
const path = [config.name];
while (top !== parent.top) {
if (top instanceof SettingField && top.type !== 'group') {
path.unshift(top.name);
while (cur !== parent.top) {
if (cur instanceof SettingField && cur.type !== 'group') {
path.unshift(cur.name);
}
top = top.parent;
cur = cur.parent;
}
return path.join('.');
}

View File

@ -1,11 +1,12 @@
import { untracked, computed, obx, engineConfig } from '@ali/lowcode-editor-core';
import { CompositeValue, isJSExpression, isJSSlot, JSSlot, SlotSchema } from '@ali/lowcode-types';
import { CompositeValue, FieldConfig, isJSExpression, isJSSlot, JSSlot, SlotSchema } from '@ali/lowcode-types';
import { uniqueId, isPlainObject, hasOwnProperty, compatStage } from '@ali/lowcode-utils';
import { PropStash } from './prop-stash';
import { valueToSource } from './value-to-source';
import { Props } from './props';
import { SlotNode, Node } from '../node';
import { TransformStage } from '../transform-stage';
import { getFocusedElement } from 'medium-editor';
export const UNSET = Symbol.for('unset');
export type UNSET = typeof UNSET;
@ -14,10 +15,40 @@ export interface IPropParent {
delete(prop: Prop): void;
readonly props: Props;
readonly owner: Node;
readonly path: string[];
}
export type ValueTypes = 'unset' | 'literal' | 'map' | 'list' | 'expression' | 'slot';
function hasItemsInMetadata(prop: Prop) {
const path = prop.path;
const { configure } = prop.getNode().componentMeta.getMetadata();
if (!path || path.length === 0) return false;
let props = configure?.props;
while (path.length > 0) {
let name = path.shift();
let matchedProp = getMatchedProp(props, name!);
if (!matchedProp) return false;
if (path.length === 0) return !!matchedProp?.items;
props = matchedProp.items;
}
}
function getMatchedProp(props: FieldConfig[] | undefined, name: string): FieldConfig | null {
let found = null;
if (!props) return null;
for (const prop of props) {
if (prop.name === name) {
found = prop;
break;
} else if (prop.type === 'group' && prop.items) {
found = getMatchedProp(prop.items, name);
if (found) return found;
}
}
return found;
}
export class Prop implements IPropParent {
readonly isProp = true;
@ -253,6 +284,21 @@ export class Prop implements IPropParent {
};
}
this.dispose();
// 将父属性设置成一个对象,得同时把子属性都设值,同时清空其他子属性
if (this._type === 'map' && isPlainObject(val)) {
this.items?.forEach((item) => {
// @ts-ignore
if ([item.key] in val) {
// @ts-ignore
item.setValue(val[item.key]);
} else {
this.clearPropValue(item.key!);
}
});
}
if (oldValue !== this._value) {
editor?.emit('node.innerProp.change', {
node: this.owner,
@ -261,7 +307,6 @@ export class Prop implements IPropParent {
newValue: this._value,
});
}
this.dispose();
}
@computed getValue(): CompositeValue {
@ -358,7 +403,13 @@ export class Prop implements IPropParent {
@obx.val private _maps: Map<string | number, Prop> | null = null;
@computed private get items(): Prop[] | null {
get path(): string[] {
return (this.parent.path || []).concat(this.key as string);
}
private get items(): Prop[] | null {
// 不在元数据配置项中的,不产生 items
if (!hasItemsInMetadata(this)) return null;
let _items: any;
untracked(() => {
_items = this._items;

View File

@ -37,6 +37,8 @@ export class Props implements IPropParent {
return maps;
}
readonly path = [];
get props(): Props {
return this;
}

View File

@ -1,9 +1,9 @@
import { Component, Fragment } from 'react';
import { Icon, Button } from '@alifd/next';
import { SetterType, FieldConfig } from '@ali/lowcode-types';
import { SetterType, FieldConfig, CustomView } from '@ali/lowcode-types';
import { createSettingFieldView } from '../settings/settings-pane';
import { PopupContext, PopupPipe } from '../popup';
import { SettingField } from '@ali/lowcode-designer';
import { isSettingField, SettingField } from '@ali/lowcode-designer';
import './style.less';
import { Title } from '@ali/lowcode-editor-core';
@ -165,11 +165,17 @@ class FormSetter extends Component<FormSetterProps> {
super(props);
const { config, field } = props;
const { extraProps } = field;
this.items = (config?.items || []).map((conf) => field.createField({
...conf,
setValue: extraProps?.setValue,
}));
field.items.forEach((item: SettingField | CustomView) => {
if (isSettingField(item)) {
const originalSetValue = item.extraProps.setValue;
item.extraProps.setValue = (...args) => {
// 调用子字段本身的 setValue
originalSetValue?.apply(null, args);
// 调用父字段本身的 setValue
extraProps.setValue?.apply(null, args);
};
}
});
// TODO: extraConfig for custom fields
}
@ -181,7 +187,7 @@ class FormSetter extends Component<FormSetterProps> {
const { field } = this.props;
return (
<div className="lc-setter-object lc-block-setter">
{this.items.map((item, index) => createSettingFieldView(item, field, index))}
{field.items.map((item, index) => createSettingFieldView(item, field, index))}
</div>
);
}

View File

@ -69,13 +69,13 @@ export class SettingsMain {
if (!this.designer) {
this.designer = nodes[0].document.designer;
}
let lastSettings = this._settings;
// obx 的一些响应式计算会延迟到下一个时钟周期,导致 prop.parent 获取不到,这里也做一个延迟
executePendingFn(() => {
lastSettings?.purge();
}, 2000);
this._settings = this.designer.createSettingEntry(nodes);
// 当节点只有一个时,复用 node 上挂载的 settingEntry不会产生平行的两个实例这样在整个系统中对
// 某个节点操作的 SettingTopEntry 只有一个实例,后续的 getProp() 也会拿到相同的 SettingField 实例
if (nodes.length === 1) {
this._settings = nodes[0].settingEntry;
} else {
this._settings = this.designer.createSettingEntry(nodes);
}
}
purge() {