mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-13 12:13:10 +00:00
412 lines
9.6 KiB
TypeScript
412 lines
9.6 KiB
TypeScript
import { computed, makeObservable, obx, action } from '@alilc/lowcode-editor-core';
|
|
import { IPublicTypePropsMap, IPublicTypePropsList, IPublicTypeCompositeValue, IPublicEnumTransformStage, IBaseModelProps } from '@alilc/lowcode-types';
|
|
import { uniqueId, compatStage } from '@alilc/lowcode-utils';
|
|
import { Prop, IProp, UNSET } from './prop';
|
|
import { INode } from '../node';
|
|
// import { TransformStage } from '../transform-stage';
|
|
|
|
interface ExtrasObject {
|
|
[key: string]: any;
|
|
}
|
|
|
|
export const EXTRA_KEY_PREFIX = '___';
|
|
export function getConvertedExtraKey(key: string): string {
|
|
if (!key) {
|
|
return '';
|
|
}
|
|
let _key = key;
|
|
if (key.indexOf('.') > 0) {
|
|
_key = key.split('.')[0];
|
|
}
|
|
return EXTRA_KEY_PREFIX + _key + EXTRA_KEY_PREFIX + key.slice(_key.length);
|
|
}
|
|
export function getOriginalExtraKey(key: string): string {
|
|
return key.replace(new RegExp(`${EXTRA_KEY_PREFIX}`, 'g'), '');
|
|
}
|
|
|
|
export interface IPropParent {
|
|
|
|
readonly props: Props;
|
|
|
|
readonly owner: INode;
|
|
|
|
get path(): string[];
|
|
|
|
delete(prop: Prop): void;
|
|
|
|
query(path: string, createIfNone: boolean): Prop | null;
|
|
}
|
|
|
|
export interface IProps extends Omit<IBaseModelProps<IProp>, | 'getExtraProp' | 'getExtraPropValue' | 'setExtraPropValue' | 'node'> {
|
|
|
|
/**
|
|
* 获取 props 对应的 node
|
|
*/
|
|
getNode(): INode;
|
|
|
|
get(path: string, createIfNone?: boolean): Prop | null;
|
|
|
|
export(stage?: IPublicEnumTransformStage): {
|
|
props?: IPublicTypePropsMap | IPublicTypePropsList;
|
|
extras?: ExtrasObject;
|
|
};
|
|
|
|
merge(value: IPublicTypePropsMap, extras?: IPublicTypePropsMap): void;
|
|
}
|
|
|
|
export class Props implements IProps, IPropParent {
|
|
readonly id = uniqueId('props');
|
|
|
|
@obx.shallow private items: Prop[] = [];
|
|
|
|
@computed private get maps(): Map<string, Prop> {
|
|
const maps = new Map();
|
|
if (this.items.length > 0) {
|
|
this.items.forEach((prop) => {
|
|
if (prop.key) {
|
|
maps.set(prop.key, prop);
|
|
}
|
|
});
|
|
}
|
|
return maps;
|
|
}
|
|
|
|
readonly path = [];
|
|
|
|
get props(): Props {
|
|
return this;
|
|
}
|
|
|
|
readonly owner: INode;
|
|
|
|
/**
|
|
* 元素个数
|
|
*/
|
|
@computed get size() {
|
|
return this.items.length;
|
|
}
|
|
|
|
@obx type: 'map' | 'list' = 'map';
|
|
|
|
private purged = false;
|
|
|
|
constructor(owner: INode, value?: IPublicTypePropsMap | IPublicTypePropsList | null, extras?: ExtrasObject) {
|
|
makeObservable(this);
|
|
this.owner = owner;
|
|
if (Array.isArray(value)) {
|
|
this.type = 'list';
|
|
this.items = value.map(
|
|
(item, idx) => new Prop(this, item.value, item.name || idx, item.spread),
|
|
);
|
|
} else if (value != null) {
|
|
this.items = Object.keys(value).map((key) => new Prop(this, value[key], key, false));
|
|
}
|
|
if (extras) {
|
|
Object.keys(extras).forEach((key) => {
|
|
this.items.push(new Prop(this, (extras as any)[key], getConvertedExtraKey(key)));
|
|
});
|
|
}
|
|
}
|
|
|
|
@action
|
|
import(value?: IPublicTypePropsMap | IPublicTypePropsList | null, extras?: ExtrasObject) {
|
|
const originItems = this.items;
|
|
if (Array.isArray(value)) {
|
|
this.type = 'list';
|
|
this.items = value.map(
|
|
(item, idx) => new Prop(this, item.value, item.name || idx, item.spread),
|
|
);
|
|
} else if (value != null) {
|
|
this.type = 'map';
|
|
this.items = Object.keys(value).map((key) => new Prop(this, value[key], key));
|
|
} else {
|
|
this.type = 'map';
|
|
this.items = [];
|
|
}
|
|
if (extras) {
|
|
Object.keys(extras).forEach((key) => {
|
|
this.items.push(new Prop(this, (extras as any)[key], getConvertedExtraKey(key)));
|
|
});
|
|
}
|
|
originItems.forEach((item) => item.purge());
|
|
}
|
|
|
|
@action
|
|
merge(value: IPublicTypePropsMap, extras?: IPublicTypePropsMap) {
|
|
Object.keys(value).forEach((key) => {
|
|
this.query(key, true)!.setValue(value[key]);
|
|
this.query(key, true)!.setupItems();
|
|
});
|
|
if (extras) {
|
|
Object.keys(extras).forEach((key) => {
|
|
this.query(getConvertedExtraKey(key), true)!.setValue(extras[key]);
|
|
this.query(getConvertedExtraKey(key), true)!.setupItems();
|
|
});
|
|
}
|
|
}
|
|
|
|
export(stage: IPublicEnumTransformStage = IPublicEnumTransformStage.Save): {
|
|
props?: IPublicTypePropsMap | IPublicTypePropsList;
|
|
extras?: ExtrasObject;
|
|
} {
|
|
stage = compatStage(stage);
|
|
if (this.items.length < 1) {
|
|
return {};
|
|
}
|
|
let allProps = {} as any;
|
|
let props: any = {};
|
|
const extras: any = {};
|
|
if (this.type === 'list') {
|
|
props = [];
|
|
this.items.forEach((item) => {
|
|
let value = item.export(stage);
|
|
let name = item.key as string;
|
|
if (name && typeof name === 'string' && name.startsWith(EXTRA_KEY_PREFIX)) {
|
|
name = getOriginalExtraKey(name);
|
|
extras[name] = value;
|
|
} else {
|
|
props.push({
|
|
spread: item.spread,
|
|
name,
|
|
value,
|
|
});
|
|
}
|
|
});
|
|
} else {
|
|
this.items.forEach((item) => {
|
|
let name = item.key as string;
|
|
if (name == null || item.isUnset() || item.isVirtual()) return;
|
|
let value = item.export(stage);
|
|
if (value != null) {
|
|
allProps[name] = value;
|
|
}
|
|
});
|
|
// compatible vision
|
|
const transformedProps = this.transformToStatic(allProps);
|
|
Object.keys(transformedProps).forEach((name) => {
|
|
const value = transformedProps[name];
|
|
if (typeof name === 'string' && name.startsWith(EXTRA_KEY_PREFIX)) {
|
|
name = getOriginalExtraKey(name);
|
|
extras[name] = value;
|
|
} else {
|
|
props[name] = value;
|
|
}
|
|
});
|
|
}
|
|
|
|
return { props, extras };
|
|
}
|
|
|
|
/**
|
|
* @deprecated
|
|
*/
|
|
/* istanbul ignore next */
|
|
private transformToStatic(props: any) {
|
|
let transducers = this.owner.componentMeta?.prototype?.options?.transducers;
|
|
if (!transducers) {
|
|
return props;
|
|
}
|
|
if (!Array.isArray(transducers)) {
|
|
transducers = [transducers];
|
|
}
|
|
props = transducers.reduce((xprops: any, transducer: any) => {
|
|
if (transducer && typeof transducer.toStatic === 'function') {
|
|
return transducer.toStatic(xprops);
|
|
}
|
|
return xprops;
|
|
}, props);
|
|
return props;
|
|
}
|
|
|
|
/**
|
|
* 根据 path 路径查询属性
|
|
*
|
|
* @param createIfNone 当没有的时候,是否创建一个
|
|
*/
|
|
@action
|
|
query(path: string, createIfNone = true): Prop | null {
|
|
return this.get(path, createIfNone);
|
|
}
|
|
|
|
/**
|
|
* 获取某个属性,如果不存在,临时获取一个待写入
|
|
* @param createIfNone 当没有的时候,是否创建一个
|
|
*/
|
|
@action
|
|
get(path: string, createIfNone = false): Prop | null {
|
|
let entry = path;
|
|
let nest = '';
|
|
const i = path.indexOf('.');
|
|
if (i > 0) {
|
|
nest = path.slice(i + 1);
|
|
if (nest) {
|
|
entry = path.slice(0, i);
|
|
}
|
|
}
|
|
|
|
let prop = this.maps.get(entry);
|
|
if (!prop && createIfNone) {
|
|
prop = new Prop(this, UNSET, entry);
|
|
this.items.push(prop);
|
|
}
|
|
|
|
if (prop) {
|
|
return nest ? prop.get(nest, createIfNone) : prop;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 删除项
|
|
*/
|
|
@action
|
|
delete(prop: Prop): void {
|
|
const i = this.items.indexOf(prop);
|
|
if (i > -1) {
|
|
this.items.splice(i, 1);
|
|
prop.purge();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 删除 key
|
|
*/
|
|
@action
|
|
deleteKey(key: string): void {
|
|
this.items = this.items.filter((item, i) => {
|
|
if (item.key === key) {
|
|
item.purge();
|
|
this.items.splice(i, 1);
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 添加值
|
|
*/
|
|
@action
|
|
add(
|
|
value: IPublicTypeCompositeValue | null,
|
|
key?: string | number,
|
|
spread = false,
|
|
options: any = {},
|
|
): Prop {
|
|
const prop = new Prop(this, value, key, spread, options);
|
|
this.items.push(prop);
|
|
return prop;
|
|
}
|
|
|
|
/**
|
|
* 是否存在 key
|
|
*/
|
|
has(key: string): boolean {
|
|
return this.maps.has(key);
|
|
}
|
|
|
|
/**
|
|
* 迭代器
|
|
*/
|
|
[Symbol.iterator](): { next(): { value: Prop } } {
|
|
let index = 0;
|
|
const { items } = this;
|
|
const length = items.length || 0;
|
|
return {
|
|
next() {
|
|
if (index < length) {
|
|
return {
|
|
value: items[index++],
|
|
done: false,
|
|
};
|
|
}
|
|
return {
|
|
value: undefined as any,
|
|
done: true,
|
|
};
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 遍历
|
|
*/
|
|
@action
|
|
forEach(fn: (item: Prop, key: number | string | undefined) => void): void {
|
|
this.items.forEach((item) => {
|
|
return fn(item, item.key);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 遍历
|
|
*/
|
|
@action
|
|
map<T>(fn: (item: Prop, key: number | string | undefined) => T): T[] | null {
|
|
return this.items.map((item) => {
|
|
return fn(item, item.key);
|
|
});
|
|
}
|
|
|
|
@action
|
|
filter(fn: (item: Prop, key: number | string | undefined) => boolean) {
|
|
return this.items.filter((item) => {
|
|
return fn(item, item.key);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 回收销毁
|
|
*/
|
|
@action
|
|
purge() {
|
|
if (this.purged) {
|
|
return;
|
|
}
|
|
this.purged = true;
|
|
this.items.forEach((item) => item.purge());
|
|
}
|
|
|
|
/**
|
|
* 获取某个属性, 如果不存在,临时获取一个待写入
|
|
* @param createIfNone 当没有的时候,是否创建一个
|
|
*/
|
|
@action
|
|
getProp(path: string, createIfNone = true): Prop | null {
|
|
return this.query(path, createIfNone) || null;
|
|
}
|
|
|
|
/**
|
|
* 获取单个属性值
|
|
*/
|
|
@action
|
|
getPropValue(path: string): any {
|
|
return this.getProp(path, false)?.value;
|
|
}
|
|
|
|
/**
|
|
* 设置单个属性值
|
|
*/
|
|
@action
|
|
setPropValue(path: string, value: any) {
|
|
this.getProp(path, true)!.setValue(value);
|
|
}
|
|
|
|
/**
|
|
* 获取 props 对应的 node
|
|
*/
|
|
getNode() {
|
|
return this.owner;
|
|
}
|
|
|
|
/**
|
|
* @deprecated
|
|
* 获取 props 对应的 node
|
|
*/
|
|
@action
|
|
toData() {
|
|
return this.export()?.props;
|
|
}
|
|
}
|