2020-03-30 04:11:05 +08:00

495 lines
11 KiB
TypeScript

import { EventEmitter } from 'events';
import { uniqueId } from '@ali/lowcode-globals';
import { ComponentMeta, Node, Designer, Selection } from '@ali/lowcode-designer';
import { TitleContent, FieldExtraProps, SetterType, CustomView, FieldConfig, isCustomView } from '@ali/lowcode-globals';
import { getTreeMaster } from 'plugin-outline-pane/src/main';
export interface SettingTarget {
// 所设置的节点集,至少一个
readonly nodes: Node[];
readonly componentMeta: ComponentMeta | null;
readonly items: Array<SettingField | CustomView>;
/**
* 同样的
*/
readonly isSame: boolean;
/**
* 一个
*/
readonly isOne: boolean;
/**
* 多个
*/
readonly isMulti: boolean;
/**
* 无
*/
readonly isNone: boolean;
/**
* 编辑器引用
*/
readonly editor: object;
readonly designer?: Designer;
readonly path: string[];
readonly top: SettingTarget;
// 响应式自动运行
onEffect(action: () => void): () => void;
// 获取属性值
getPropValue(propName: string | number): any;
// 设置属性值
setPropValue(propName: string | number, value: any): void;
// 获取附属属性值
getExtraPropValue(propName: string): any;
// 设置附属属性值
setExtraPropValue(propName: string, value: any): void;
}
export class SettingField implements SettingTarget {
readonly isSettingField = true;
readonly id = uniqueId('field');
readonly type: 'field' | 'group';
readonly isRequired: boolean = false;
readonly isGroup: boolean;
private _name: string | number;
get name() {
return this._name;
}
readonly title: TitleContent;
readonly editor: any;
readonly extraProps: FieldExtraProps;
readonly setter?: SetterType;
readonly isSame: boolean;
readonly isMulti: boolean;
readonly isOne: boolean;
readonly isNone: boolean;
readonly nodes: Node[];
readonly componentMeta: ComponentMeta | null;
readonly designer: Designer;
readonly top: SettingTarget;
get path() {
const path = this.parent.path.slice();
if (this.type === 'field') {
path.push(String(this.name));
}
return path;
}
constructor(readonly parent: SettingTarget, config: FieldConfig) {
const { type, title, name, items, setter, extraProps, ...rest } = config;
if (type == null) {
const c = typeof name === 'string' ? name.substr(0, 1) : '';
if (c === '#') {
this.type = 'group';
} else {
this.type = 'field';
}
} else {
this.type = type;
}
// initial self properties
this._name = name;
// make this reactive
this.title = title || (typeof name === 'number' ? `项目 ${name}` : name);
this.setter = setter;
this.extraProps = {
...rest,
...extraProps,
};
this.isRequired = config.isRequired || (setter as any)?.isRequired;
this.isGroup = this.type === 'group';
// copy parent properties
this.editor = parent.editor;
this.nodes = parent.nodes;
this.componentMeta = parent.componentMeta;
this.isSame = parent.isSame;
this.isMulti = parent.isMulti;
this.isOne = parent.isOne;
this.isNone = parent.isNone;
this.designer = parent.designer!;
this.top = parent.top;
// initial items
if (this.type === 'group' && items) {
this.initItems(items);
}
}
onEffect(action: () => void): () => void {
return this.designer.autorun(action, true);
}
private _items: Array<SettingField | CustomView> = [];
private initItems(items: Array<FieldConfig | CustomView>) {
this._items = items.map((item) => {
if (isCustomView(item)) {
return item;
}
return new SettingField(this, item);
});
}
private disposeItems() {
this._items.forEach(item => isSettingField(item) && item.purge());
this._items = [];
}
createField(config: FieldConfig): SettingField {
return new SettingField(this, config);
}
get items() {
return this._items;
}
// ====== 当前属性读写 =====
/**
* 判断当前属性值是否一致
* 0 无值/多种值
* 1 类似值,比如数组长度一样
* 2 单一植
*/
get valueState(): number {
if (this.type !== 'field') {
return 0;
}
const propName = this.path.join('.');
const first = this.nodes[0].getProp(propName)!;
let l = this.nodes.length;
let state = 2;
while (l-- > 1) {
const next = this.nodes[l].getProp(propName, false);
const s = first.compare(next);
if (s > 1) {
return 0;
}
if (s === 1) {
state = 1;
}
}
return state;
}
/**
* 获取当前属性值
*/
getValue(): any {
let val: any = null;
if (this.type === 'field') {
val = this.parent.getPropValue(this.name);
}
const { getValue } = this.extraProps;
return getValue ? getValue(this, val) : val;
}
/**
* 设置当前属性值
*/
setValue(val: any) {
if (this.type === 'field') {
this.parent.setPropValue(this.name, val);
}
const { setValue } = this.extraProps;
if (setValue) {
setValue(this, val);
}
}
setKey(key: string | number) {
if (this.type !== 'field') {
return;
}
const propName = this.path.join('.');
let l = this.nodes.length;
while (l-- > 1) {
this.nodes[l].getProp(propName, true)!.key = key;
}
this._name = key;
}
remove() {
if (this.type !== 'field') {
return;
}
const propName = this.path.join('.');
let l = this.nodes.length;
while (l-- > 1) {
this.nodes[l].getProp(propName)?.remove()
}
}
/**
* 设置子级属性值
*/
setPropValue(propName: string | number, value: any) {
const path = this.type === 'field' ? `${this.name}.${propName}` : propName;
this.parent.setPropValue(path, value);
}
/**
* 获取子级属性值
*/
getPropValue(propName: string | number): any {
const path = this.type === 'field' ? `${this.name}.${propName}` : propName;
return this.parent.getPropValue(path);
}
getExtraPropValue(propName: string) {
return this.top.getExtraPropValue(propName);
}
setExtraPropValue(propName: string, value: any) {
this.top.setExtraPropValue(propName, value);
}
purge() {
this.disposeItems();
}
}
export function isSettingField(obj: any): obj is SettingField {
return obj && obj.isSettingField;
}
export class SettingsMain implements SettingTarget {
private emitter = new EventEmitter();
private _nodes: Node[] = [];
private _items: Array<SettingField | CustomView> = [];
private _sessionId = '';
private _componentMeta: ComponentMeta | null = null;
private _isSame: boolean = true;
readonly path = [];
readonly top: SettingTarget = this;
get nodes(): Node[] {
return this._nodes;
}
get componentMeta() {
return this._componentMeta;
}
get items() {
return this._items;
}
/**
* 同样的
*/
get isSame(): boolean {
return this._isSame;
}
/**
* 一个
*/
get isOne(): boolean {
return this.nodes.length === 1;
}
/**
* 多个
*/
get isMulti(): boolean {
return this.nodes.length > 1;
}
/**
* 无
*/
get isNone() {
return this.nodes.length < 1;
}
private disposeListener: () => void;
private _designer?: Designer;
get designer() {
return this._designer || this.editor.designer;
}
constructor(readonly editor: any) {
const setupSelection = (selection?: Selection) => {
if (selection) {
if (!this._designer) {
this._designer = selection.doc.designer;
}
this.setup(selection.getNodes());
} else {
this.setup([]);
}
};
editor.on('designer.selection-change', setupSelection);
const connectTree = (designer: any) => {
getTreeMaster(designer).onceEnableBuiltin(() => {
this.emitter.emit('outline-visible');
});
}
if (editor.designer) {
connectTree(editor.designer);
setupSelection(editor.designer.currentSelection);
} else {
editor.once('designer.mount', connectTree);
}
this.disposeListener = () => {
editor.removeListener('designer.selection-change', setupSelection);
};
}
onEffect(action: () => void): () => void {
action();
return this.onNodesChange(action);
}
onceOutlineVisible(fn: () => void): () => void {
this.emitter.on('outline-visible', fn);
return () => {
this.emitter.removeListener('outline-visible', fn);
};
}
/**
* 获取属性值
*/
getPropValue(propName: string): any {
if (this.nodes.length < 1) {
return null;
}
return this.nodes[0].getProp(propName, true)?.getValue();
}
/**
* 设置属性值
*/
setPropValue(propName: string, value: any) {
this.nodes.forEach(node => {
node.setPropValue(propName, value);
});
}
getExtraPropValue(propName: string) {
if (this.nodes.length < 1) {
return null;
}
return this.nodes[0].getExtraProp(propName, false)?.getValue();
}
setExtraPropValue(propName: string, value: any) {
this.nodes.forEach(node => {
node.getExtraProp(propName, true)?.setValue(value);
});
}
// 设置多个属性值,替换原有值
setProps(data: object) {
this.nodes.forEach(node => {
node.setProps(data as any);
});
}
// 设置多个属性值,和原有值合并
mergeProps(data: object) {
this.nodes.forEach(node => {
node.mergeProps(data as any);
});
}
private setup(nodes: Node[]) {
this._nodes = nodes;
// check nodes change
const sessionId = this.nodes
.map(node => node.id)
.sort()
.join(',');
if (sessionId === this._sessionId) {
return;
}
this._sessionId = sessionId;
// setups
this.setupComponentMeta();
// todo: enhance when componentType not changed do merge
// clear fields
this.setupItems();
// emit change
this.emitter.emit('nodeschange');
}
private disposeItems() {
this._items.forEach(item => isSettingField(item) && item.purge());
this._items = [];
}
private setupComponentMeta() {
if (this.nodes.length < 1) {
this._isSame = false;
this._componentMeta = null;
return;
}
const first = this.nodes[0];
const meta = first.componentMeta;
const l = this.nodes.length;
let theSame = true;
for (let i = 1; i < l; i++) {
const other = this.nodes[i];
if (other.componentMeta !== meta) {
theSame = false;
break;
}
}
if (theSame) {
this._isSame = true;
this._componentMeta = meta;
} else {
this._isSame = false;
this._componentMeta = null;
}
}
private setupItems() {
this.disposeItems();
if (this.componentMeta) {
this._items = this.componentMeta.configure.map(item => {
if (isCustomView(item)) {
return item;
}
return new SettingField(this, item as any);
});
}
}
onNodesChange(fn: () => void): () => void {
this.emitter.on('nodeschange', fn);
return () => {
this.emitter.removeListener('nodeschange', fn);
};
}
purge() {
this.disposeListener();
this.disposeItems();
this.emitter.removeAllListeners();
}
}