mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-03-06 18:37:07 +00:00
optimize settings pane
This commit is contained in:
parent
4da47f2300
commit
92ea6505c1
@ -24,7 +24,6 @@ export class Field extends Component<FieldProps> {
|
|||||||
|
|
||||||
export interface FieldGroupProps extends FieldProps {
|
export interface FieldGroupProps extends FieldProps {
|
||||||
defaultCollapsed?: boolean;
|
defaultCollapsed?: boolean;
|
||||||
// gap?: number;
|
|
||||||
onExpandChange?: (collapsed: boolean) => void;
|
onExpandChange?: (collapsed: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,154 +1,9 @@
|
|||||||
import React, { Component, PureComponent } from 'react';
|
import { createSettingFieldView } from './settings/settings-pane';
|
||||||
import { Tab, Breadcrumb } from '@alifd/next';
|
|
||||||
import { Title, createIcon } from '@ali/lowcode-globals';
|
|
||||||
import { Node } from '@ali/lowcode-designer';
|
|
||||||
import OutlinePane from '@ali/lowcode-plugin-outline-pane';
|
|
||||||
import { SettingsMain, SettingField, isSettingField } from './main';
|
|
||||||
import SettingsPane, { createSettingFieldView } from './settings-pane';
|
|
||||||
import './transducers/register';
|
import './transducers/register';
|
||||||
import './setters/register';
|
import './setters/register';
|
||||||
import './style.less';
|
import './style.less';
|
||||||
|
import SettingsMainView from './settings/main-view';
|
||||||
|
|
||||||
export default class SettingsMainView extends Component {
|
export default SettingsMainView;
|
||||||
private main: SettingsMain;
|
|
||||||
|
|
||||||
constructor(props: any) {
|
|
||||||
super(props);
|
|
||||||
this.main = new SettingsMain(props.editor);
|
|
||||||
this.main.onNodesChange(() => {
|
|
||||||
this.forceUpdate();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldComponentUpdate() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.main.purge();
|
|
||||||
}
|
|
||||||
|
|
||||||
renderBreadcrumb() {
|
|
||||||
if (this.main.isMulti) {
|
|
||||||
return (
|
|
||||||
<div className="lc-settings-navigator">
|
|
||||||
{createIcon(this.main.componentMeta?.icon, { className: 'lc-settings-navigator-icon'})}
|
|
||||||
<Title title={this.main.componentMeta!.title} />
|
|
||||||
<span>x {this.main.nodes.length}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let node: Node | null = this.main.nodes[0]!;
|
|
||||||
const items = [];
|
|
||||||
let l = 3;
|
|
||||||
while (l-- > 0 && node) {
|
|
||||||
const props =
|
|
||||||
l === 2
|
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
onMouseOver: hoverNode.bind(null, node, true),
|
|
||||||
onMouseOut: hoverNode.bind(null, node, false),
|
|
||||||
onClick: selectNode.bind(null, node),
|
|
||||||
};
|
|
||||||
items.unshift(<Breadcrumb.Item {...props} key={node.id}><Title title={node.title} /></Breadcrumb.Item>);
|
|
||||||
node = node.parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="lc-settings-navigator">
|
|
||||||
{createIcon(this.main.componentMeta?.icon, { className: 'lc-settings-navigator-icon'})}
|
|
||||||
<Breadcrumb className="lc-settings-node-breadcrumb">{items}</Breadcrumb>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (this.main.isNone) {
|
|
||||||
// 未选中节点,提示选中 或者 显示根节点设置
|
|
||||||
return (
|
|
||||||
<div className="lc-settings-main">
|
|
||||||
<OutlinePaneEntry main={this.main} />
|
|
||||||
<div className="lc-settings-notice">
|
|
||||||
<p>请在左侧画布选中节点</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.main.isSame) {
|
|
||||||
// todo: future support 获取设置项交集编辑
|
|
||||||
return (
|
|
||||||
<div className="lc-settings-main">
|
|
||||||
<OutlinePaneEntry main={this.main} />
|
|
||||||
<div className="lc-settings-notice">
|
|
||||||
<p>请选中同一类型节点编辑</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { items } = this.main;
|
|
||||||
if (items.length > 5 || items.some(item => !isSettingField(item) || !item.isGroup)) {
|
|
||||||
return (
|
|
||||||
<div className="lc-settings-main">
|
|
||||||
<OutlinePaneEntry main={this.main} />
|
|
||||||
{this.renderBreadcrumb()}
|
|
||||||
<div className="lc-settings-body">
|
|
||||||
<SettingsPane target={this.main} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="lc-settings-main">
|
|
||||||
<OutlinePaneEntry main={this.main} />
|
|
||||||
<Tab
|
|
||||||
navClassName="lc-settings-tabs"
|
|
||||||
animation={false}
|
|
||||||
excessMode="dropdown"
|
|
||||||
contentClassName="lc-settings-tabs-content"
|
|
||||||
extra={this.renderBreadcrumb()}
|
|
||||||
>
|
|
||||||
{(items as SettingField[]).map(field => (
|
|
||||||
<Tab.Item className="lc-settings-tab-item" title={<Title title={field.title} />} key={field.name}>
|
|
||||||
<SettingsPane target={field} key={field.id} />
|
|
||||||
</Tab.Item>
|
|
||||||
))}
|
|
||||||
</Tab>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class OutlinePaneEntry extends PureComponent<{ main: SettingsMain }> {
|
|
||||||
state = {
|
|
||||||
outlineInited: false,
|
|
||||||
};
|
|
||||||
private dispose = this.props.main.onceOutlineVisible(() => {
|
|
||||||
this.setState({
|
|
||||||
outlineInited: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.dispose();
|
|
||||||
}
|
|
||||||
render() {
|
|
||||||
if (!this.state.outlineInited) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return <OutlinePane editor={this.props.main.editor} config={{
|
|
||||||
name: '__IN_SETTINGS__'
|
|
||||||
}} />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function hoverNode(node: Node, flag: boolean) {
|
|
||||||
node.hover(flag);
|
|
||||||
}
|
|
||||||
function selectNode(node: Node) {
|
|
||||||
node.select();
|
|
||||||
}
|
|
||||||
|
|
||||||
export { createSettingFieldView };
|
export { createSettingFieldView };
|
||||||
|
|||||||
@ -1,525 +0,0 @@
|
|||||||
import { EventEmitter } from 'events';
|
|
||||||
import { uniqueId, DynamicSetter, isDynamicSetter } 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 '@ali/lowcode-plugin-outline-pane';
|
|
||||||
import Editor from '@ali/lowcode-editor-core';
|
|
||||||
import { Transducer } from './utils';
|
|
||||||
|
|
||||||
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;
|
|
||||||
private _setter?: SetterType | DynamicSetter;
|
|
||||||
get setter(): SetterType | null {
|
|
||||||
if (!this._setter) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (isDynamicSetter(this._setter)) {
|
|
||||||
return this._setter(this);
|
|
||||||
}
|
|
||||||
return this._setter;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
readonly transducer: Transducer;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.transducer = new Transducer(this, { setter });
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======= compatibles ====
|
|
||||||
getHotValue(): any {
|
|
||||||
return this.transducer.toHot(this.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
setHotValue(data: any) {
|
|
||||||
this.setValue(this.transducer.toNative(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
getNode() {
|
|
||||||
return this.nodes[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
getProps() {
|
|
||||||
return this.parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
onValueChange() {
|
|
||||||
return () => {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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.get(Designer);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(readonly editor: Editor) {
|
|
||||||
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);
|
|
||||||
this.disposeListener = () => {
|
|
||||||
editor.removeListener('designer.selection.change', setupSelection);
|
|
||||||
};
|
|
||||||
(async () => {
|
|
||||||
const designer = await editor.onceGot(Designer);
|
|
||||||
getTreeMaster(designer).onceEnableBuiltin(() => {
|
|
||||||
this.emitter.emit('outline-visible');
|
|
||||||
});
|
|
||||||
setupSelection(designer.currentSelection);
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import { Component, Fragment } from 'react';
|
import { Component, Fragment } from 'react';
|
||||||
import { Icon, Button, Message } from '@alifd/next';
|
import { Icon, Button, Message } from '@alifd/next';
|
||||||
import { Title, SetterType, FieldConfig, SetterConfig } from '@ali/lowcode-globals';
|
import { Title, SetterType, FieldConfig, SetterConfig } from '@ali/lowcode-globals';
|
||||||
import { SettingField } from '../../main';
|
import { SettingField } from '../../settings/setting-field';
|
||||||
import { createSettingFieldView } from '../../settings-pane';
|
import { createSettingFieldView } from '../../settings/settings-pane';
|
||||||
import { PopupContext, PopupPipe } from '../../popup';
|
import { PopupContext, PopupPipe } from '../../popup';
|
||||||
import Sortable from './sortable';
|
import Sortable from './sortable';
|
||||||
import './style.less';
|
import './style.less';
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import React, { PureComponent, Component } from 'react';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Dropdown, Button, Menu, Icon } from '@alifd/next';
|
import { Dropdown, Button, Menu, Icon } from '@alifd/next';
|
||||||
import { getSetter, getSettersMap, SetterConfig, computed, obx, CustomView, DynamicProps, DynamicSetter, TitleContent, isSetterConfig, Title, createSetterContent } from '@ali/lowcode-globals';
|
import { getSetter, getSettersMap, SetterConfig, computed, obx, CustomView, DynamicProps, DynamicSetter, TitleContent, isSetterConfig, Title, createSetterContent } from '@ali/lowcode-globals';
|
||||||
import { SettingField } from 'plugin-settings-pane/src/main';
|
import { SettingField } from 'plugin-settings-pane/src/settings/main';
|
||||||
|
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ function nomalizeSetters(setters?: Array<string | SetterConfig | CustomView | Dy
|
|||||||
export default class MixedSetter extends Component<{
|
export default class MixedSetter extends Component<{
|
||||||
field: SettingField;
|
field: SettingField;
|
||||||
setters?: Array<string | SetterConfig | CustomView | DynamicSetter>;
|
setters?: Array<string | SetterConfig | CustomView | DynamicSetter>;
|
||||||
onSetterChange: (field: SettingField, name: string) => void;
|
onSetterChange?: (field: SettingField, name: string) => void;
|
||||||
}> {
|
}> {
|
||||||
private setters = nomalizeSetters(this.props.setters);
|
private setters = nomalizeSetters(this.props.setters);
|
||||||
@obx.ref private used?: string;
|
@obx.ref private used?: string;
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { Component, Fragment } from 'react';
|
import { Component, Fragment } from 'react';
|
||||||
import { Icon, Button } from '@alifd/next';
|
import { Icon, Button } from '@alifd/next';
|
||||||
import { Title, SetterType, FieldConfig } from '@ali/lowcode-globals';
|
import { Title, SetterType, FieldConfig } from '@ali/lowcode-globals';
|
||||||
import { SettingField } from '../../main';
|
import { createSettingFieldView } from '../../settings/settings-pane';
|
||||||
import { createSettingFieldView } from '../../settings-pane';
|
import { SettingField } from '../../settings/setting-field';
|
||||||
import { PopupContext, PopupPipe } from '../../popup';
|
import { PopupContext, PopupPipe } from '../../popup';
|
||||||
import './style.less';
|
import './style.less';
|
||||||
|
|
||||||
|
|||||||
@ -1,246 +0,0 @@
|
|||||||
import { Component } from 'react';
|
|
||||||
import {
|
|
||||||
createContent,
|
|
||||||
CustomView,
|
|
||||||
DynamicProps,
|
|
||||||
intl,
|
|
||||||
shallowIntl,
|
|
||||||
isSetterConfig,
|
|
||||||
createSetterContent,
|
|
||||||
shallowEqual,
|
|
||||||
} from '@ali/lowcode-globals';
|
|
||||||
import { SettingField, isSettingField, SettingTarget } from './main';
|
|
||||||
import { Field, FieldGroup } from './field';
|
|
||||||
import PopupService from './popup';
|
|
||||||
|
|
||||||
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 { setter } = field;
|
|
||||||
let setterProps: object | DynamicProps = {};
|
|
||||||
if (Array.isArray(setter)) {
|
|
||||||
this.setterType = 'MixedSetter';
|
|
||||||
// setterProps =
|
|
||||||
}
|
|
||||||
if (isSetterConfig(setter)) {
|
|
||||||
this.setterType = setter.componentName;
|
|
||||||
if (setter.props) {
|
|
||||||
setterProps = setter.props;
|
|
||||||
}
|
|
||||||
} else if (setter) {
|
|
||||||
this.setterType = setter;
|
|
||||||
}
|
|
||||||
let firstRun = true;
|
|
||||||
this.dispose = field.onEffect(() => {
|
|
||||||
const state: any = {};
|
|
||||||
const { extraProps } = field;
|
|
||||||
const { condition, defaultValue } = extraProps;
|
|
||||||
state.visible = field.isOne && typeof condition === 'function' ? !condition(field) : true;
|
|
||||||
if (state.visible) {
|
|
||||||
state.setterProps = {
|
|
||||||
...(typeof setterProps === 'function' ? setterProps(field) : setterProps),
|
|
||||||
};
|
|
||||||
if (field.type === 'field') {
|
|
||||||
if (defaultValue != null && !('defaultValue' in state.setterProps)) {
|
|
||||||
state.setterProps.defaultValue = defaultValue;
|
|
||||||
}
|
|
||||||
if (field.valueState > 0) {
|
|
||||||
state.value = field.getValue();
|
|
||||||
} else {
|
|
||||||
state.value = null;
|
|
||||||
state.setterProps.multiValue = true;
|
|
||||||
if (!('placeholder' in props)) {
|
|
||||||
state.setterProps.placeholder = intl({
|
|
||||||
type: 'i18n',
|
|
||||||
'zh-CN': '多种值',
|
|
||||||
'en-US': 'Multiple Value',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO: error handling
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (firstRun) {
|
|
||||||
firstRun = false;
|
|
||||||
this.state = state;
|
|
||||||
} else {
|
|
||||||
this.setState(state);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldComponentUpdate(_: any, nextState: any) {
|
|
||||||
const { state } = this;
|
|
||||||
if (
|
|
||||||
nextState.value !== state.value ||
|
|
||||||
nextState.visible !== state.visible ||
|
|
||||||
!shallowEqual(state.setterProps, nextState.setterProps)
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { visible, value, setterProps } = this.state;
|
|
||||||
if (!visible) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const { field } = this.props;
|
|
||||||
const { title, extraProps } = field;
|
|
||||||
|
|
||||||
// todo: error handling
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Field title={extraProps.forceInline ? null : title}>
|
|
||||||
{createSetterContent(this.setterType, {
|
|
||||||
// TODO: refresh intl
|
|
||||||
...shallowIntl(setterProps),
|
|
||||||
forceInline: extraProps.forceInline,
|
|
||||||
key: field.id,
|
|
||||||
// === injection
|
|
||||||
prop: field, // for compatible vision
|
|
||||||
field,
|
|
||||||
// === IO
|
|
||||||
value, // reaction point
|
|
||||||
onChange: (value: any) => {
|
|
||||||
this.setState({
|
|
||||||
value,
|
|
||||||
});
|
|
||||||
field.setValue(value);
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
</Field>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SettingGroupView extends Component<{ field: SettingField }> {
|
|
||||||
state = {
|
|
||||||
visible: false,
|
|
||||||
items: [],
|
|
||||||
};
|
|
||||||
private dispose: () => void;
|
|
||||||
constructor(props: any) {
|
|
||||||
super(props);
|
|
||||||
const { field } = this.props;
|
|
||||||
const { condition } = field.extraProps;
|
|
||||||
let firstRun = true;
|
|
||||||
this.dispose = field.onEffect(() => {
|
|
||||||
const state: any = {};
|
|
||||||
state.visible = field.isOne && typeof condition === 'function' ? !condition(field) : true;
|
|
||||||
if (state.visible) {
|
|
||||||
state.items = field.items.slice();
|
|
||||||
}
|
|
||||||
if (firstRun) {
|
|
||||||
firstRun = false;
|
|
||||||
this.state = state;
|
|
||||||
} else {
|
|
||||||
this.setState(state);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldComponentUpdate(_: any, nextState: any) {
|
|
||||||
// todo: shallowEqual ?
|
|
||||||
if (nextState.items !== this.state.items || nextState.visible !== this.state.visible) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { field } = this.props;
|
|
||||||
const { title, extraProps } = field;
|
|
||||||
const { defaultCollapsed } = extraProps;
|
|
||||||
const { visible, items } = this.state;
|
|
||||||
// reaction point
|
|
||||||
if (!visible) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FieldGroup title={title} defaultCollapsed={defaultCollapsed}>
|
|
||||||
{items.map((item, index) => createSettingFieldView(item, field, index))}
|
|
||||||
</FieldGroup>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createSettingFieldView(item: SettingField | CustomView, field: SettingTarget, index?: number) {
|
|
||||||
if (isSettingField(item)) {
|
|
||||||
if (item.isGroup) {
|
|
||||||
return <SettingGroupView field={item} key={item.id} />;
|
|
||||||
} else {
|
|
||||||
return <SettingFieldView field={item} key={item.id} />;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return createContent(item, { key: index, field });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class SettingsPane extends Component<{ target: SettingTarget }> {
|
|
||||||
state: { items: Array<SettingField | CustomView> } = {
|
|
||||||
items: [],
|
|
||||||
};
|
|
||||||
private dispose: () => void;
|
|
||||||
constructor(props: any) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
const { target } = this.props;
|
|
||||||
let firstRun = true;
|
|
||||||
this.dispose = target.onEffect(() => {
|
|
||||||
const state = {
|
|
||||||
items: target.items.slice(),
|
|
||||||
};
|
|
||||||
if (firstRun) {
|
|
||||||
firstRun = false;
|
|
||||||
this.state = state;
|
|
||||||
} else {
|
|
||||||
this.setState(state);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldComponentUpdate(_: any, nextState: any) {
|
|
||||||
if (nextState.items !== this.state.items) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { items } = this.state;
|
|
||||||
const { target } = this.props;
|
|
||||||
return (
|
|
||||||
<div className="lc-settings-pane">
|
|
||||||
{/* todo: add head for single use */}
|
|
||||||
<PopupService>
|
|
||||||
<div className="lc-settings-content">
|
|
||||||
{items.map((item, index) => createSettingFieldView(item, target, index))}
|
|
||||||
</div>
|
|
||||||
</PopupService>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
149
packages/plugin-settings-pane/src/settings/main-view.tsx
Normal file
149
packages/plugin-settings-pane/src/settings/main-view.tsx
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
import React, { Component, PureComponent } from 'react';
|
||||||
|
import { Tab, Breadcrumb } from '@alifd/next';
|
||||||
|
import { Title, createIcon, observer } from '@ali/lowcode-globals';
|
||||||
|
import { Node } from '@ali/lowcode-designer';
|
||||||
|
import OutlinePane from '@ali/lowcode-plugin-outline-pane';
|
||||||
|
import Editor from '@ali/lowcode-editor-core';
|
||||||
|
import { SettingsMain } from './main';
|
||||||
|
import SettingsPane from './settings-pane';
|
||||||
|
import { isSettingField, SettingField } from './setting-field';
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export default class SettingsMainView extends Component<{ editor: Editor }> {
|
||||||
|
private main = new SettingsMain(this.props.editor);
|
||||||
|
|
||||||
|
shouldComponentUpdate() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.main.purge();
|
||||||
|
}
|
||||||
|
|
||||||
|
renderBreadcrumb() {
|
||||||
|
const { settings } = this.main;
|
||||||
|
if (!settings) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (settings.isMultiNodes) {
|
||||||
|
return (
|
||||||
|
<div className="lc-settings-navigator">
|
||||||
|
{createIcon(settings.componentMeta?.icon, { className: 'lc-settings-navigator-icon'})}
|
||||||
|
<Title title={settings.componentMeta!.title} />
|
||||||
|
<span>x {settings.nodes.length}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let node: Node | null = settings.first;
|
||||||
|
const items = [];
|
||||||
|
let l = 3;
|
||||||
|
while (l-- > 0 && node) {
|
||||||
|
const props =
|
||||||
|
l === 2
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
onMouseOver: hoverNode.bind(null, node, true),
|
||||||
|
onMouseOut: hoverNode.bind(null, node, false),
|
||||||
|
onClick: selectNode.bind(null, node),
|
||||||
|
};
|
||||||
|
items.unshift(<Breadcrumb.Item {...props} key={node.id}><Title title={node.title} /></Breadcrumb.Item>);
|
||||||
|
node = node.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="lc-settings-navigator">
|
||||||
|
{createIcon(this.main.componentMeta?.icon, { className: 'lc-settings-navigator-icon'})}
|
||||||
|
<Breadcrumb className="lc-settings-node-breadcrumb">{items}</Breadcrumb>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { settings } = this.main;
|
||||||
|
if (!settings) {
|
||||||
|
// 未选中节点,提示选中 或者 显示根节点设置
|
||||||
|
return (
|
||||||
|
<div className="lc-settings-main">
|
||||||
|
<OutlinePaneEntry main={this.main} />
|
||||||
|
<div className="lc-settings-notice">
|
||||||
|
<p>请在左侧画布选中节点</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!settings.isSameComponent) {
|
||||||
|
// todo: future support 获取设置项交集编辑
|
||||||
|
return (
|
||||||
|
<div className="lc-settings-main">
|
||||||
|
<OutlinePaneEntry main={this.main} />
|
||||||
|
<div className="lc-settings-notice">
|
||||||
|
<p>请选中同一类型节点编辑</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { items } = settings;
|
||||||
|
if (items.length > 5 || items.some(item => !isSettingField(item) || !item.isGroup)) {
|
||||||
|
return (
|
||||||
|
<div className="lc-settings-main">
|
||||||
|
<OutlinePaneEntry main={this.main} />
|
||||||
|
{this.renderBreadcrumb()}
|
||||||
|
<div className="lc-settings-body">
|
||||||
|
<SettingsPane target={settings} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="lc-settings-main">
|
||||||
|
<OutlinePaneEntry main={this.main} />
|
||||||
|
<Tab
|
||||||
|
navClassName="lc-settings-tabs"
|
||||||
|
animation={false}
|
||||||
|
excessMode="dropdown"
|
||||||
|
contentClassName="lc-settings-tabs-content"
|
||||||
|
extra={this.renderBreadcrumb()}
|
||||||
|
>
|
||||||
|
{(items as SettingField[]).map(field => (
|
||||||
|
<Tab.Item className="lc-settings-tab-item" title={<Title title={field.title} />} key={field.name}>
|
||||||
|
<SettingsPane target={field} key={field.id} />
|
||||||
|
</Tab.Item>
|
||||||
|
))}
|
||||||
|
</Tab>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OutlinePaneEntry extends PureComponent<{ main: SettingsMain }> {
|
||||||
|
state = {
|
||||||
|
outlineInited: false,
|
||||||
|
};
|
||||||
|
private dispose = this.props.main.onceOutlineVisible(() => {
|
||||||
|
this.setState({
|
||||||
|
outlineInited: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.dispose();
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
if (!this.state.outlineInited) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return <OutlinePane editor={this.props.main.editor} config={{
|
||||||
|
name: '__IN_SETTINGS__'
|
||||||
|
}} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hoverNode(node: Node, flag: boolean) {
|
||||||
|
node.hover(flag);
|
||||||
|
}
|
||||||
|
function selectNode(node: Node) {
|
||||||
|
node.select();
|
||||||
|
}
|
||||||
74
packages/plugin-settings-pane/src/settings/main.ts
Normal file
74
packages/plugin-settings-pane/src/settings/main.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { EventEmitter } from 'events';
|
||||||
|
import { obx, computed } from '@ali/lowcode-globals';
|
||||||
|
import { Node, Designer, Selection } from '@ali/lowcode-designer';
|
||||||
|
import { getTreeMaster } from '@ali/lowcode-plugin-outline-pane';
|
||||||
|
import Editor from '@ali/lowcode-editor-core';
|
||||||
|
import { SettingTopEntry, generateSessionId } from './setting-entry';
|
||||||
|
|
||||||
|
export class SettingsMain {
|
||||||
|
private emitter = new EventEmitter();
|
||||||
|
private _sessionId = '';
|
||||||
|
@obx.ref private _settings?: SettingTopEntry;
|
||||||
|
|
||||||
|
@computed get length(): number | undefined {
|
||||||
|
return this._settings?.nodes.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get componentMeta() {
|
||||||
|
return this._settings?.componentMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
get settings() {
|
||||||
|
return this._settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
private disposeListener: () => void;
|
||||||
|
|
||||||
|
constructor(readonly editor: Editor) {
|
||||||
|
const setupSelection = (selection?: Selection) => {
|
||||||
|
if (selection) {
|
||||||
|
this.setup(selection.getNodes());
|
||||||
|
} else {
|
||||||
|
this.setup([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
editor.on('designer.selection.change', setupSelection);
|
||||||
|
this.disposeListener = () => {
|
||||||
|
editor.removeListener('designer.selection.change', setupSelection);
|
||||||
|
};
|
||||||
|
(async () => {
|
||||||
|
const designer = await editor.onceGot(Designer);
|
||||||
|
getTreeMaster(designer).onceEnableBuiltin(() => {
|
||||||
|
this.emitter.emit('outline-visible');
|
||||||
|
});
|
||||||
|
setupSelection(designer.currentSelection);
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setup(nodes: Node[]) {
|
||||||
|
// check nodes change
|
||||||
|
const sessionId = generateSessionId(nodes);
|
||||||
|
if (sessionId === this._sessionId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._sessionId = sessionId;
|
||||||
|
if (nodes.length < 1) {
|
||||||
|
this._settings = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._settings = new SettingTopEntry(this.editor, nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
onceOutlineVisible(fn: () => void): () => void {
|
||||||
|
this.emitter.on('outline-visible', fn);
|
||||||
|
return () => {
|
||||||
|
this.emitter.removeListener('outline-visible', fn);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
purge() {
|
||||||
|
this.disposeListener();
|
||||||
|
this.emitter.removeAllListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
394
packages/plugin-settings-pane/src/settings/setting-entry.ts
Normal file
394
packages/plugin-settings-pane/src/settings/setting-entry.ts
Normal file
@ -0,0 +1,394 @@
|
|||||||
|
import { EventEmitter } from 'events';
|
||||||
|
import { Node, ComponentMeta, Designer } from '@ali/lowcode-designer';
|
||||||
|
import { CustomView, obx, uniqueId, computed, isCustomView } from '@ali/lowcode-globals';
|
||||||
|
import Editor from '@ali/lowcode-editor-core';
|
||||||
|
import { SettingTarget } from './setting-target';
|
||||||
|
import { SettingField } from './setting-field';
|
||||||
|
|
||||||
|
export function generateSessionId(nodes: Node[]) {
|
||||||
|
return nodes
|
||||||
|
.map((node) => node.id)
|
||||||
|
.sort()
|
||||||
|
.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SettingTopEntry implements SettingTarget {
|
||||||
|
private emitter = new EventEmitter();
|
||||||
|
private _items: Array<SettingField | CustomView> = [];
|
||||||
|
private _componentMeta: ComponentMeta | null = null;
|
||||||
|
private _isSame: boolean = true;
|
||||||
|
readonly path = [];
|
||||||
|
readonly top = this;
|
||||||
|
readonly parent = this;
|
||||||
|
|
||||||
|
get componentMeta() {
|
||||||
|
return this._componentMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
get items() {
|
||||||
|
return this._items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同样的
|
||||||
|
*/
|
||||||
|
get isSameComponent(): boolean {
|
||||||
|
return this._isSame;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一个
|
||||||
|
*/
|
||||||
|
get isOneNode(): boolean {
|
||||||
|
return this.nodes.length === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 多个
|
||||||
|
*/
|
||||||
|
get isMultiNodes(): boolean {
|
||||||
|
return this.nodes.length > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly id: string;
|
||||||
|
readonly first: Node;
|
||||||
|
readonly designer: Designer;
|
||||||
|
|
||||||
|
constructor(readonly editor: Editor, readonly nodes: Node[], ) {
|
||||||
|
if (nodes.length < 1) {
|
||||||
|
throw new ReferenceError('nodes should not be empty');
|
||||||
|
}
|
||||||
|
this.id = generateSessionId(nodes);
|
||||||
|
this.first = nodes[0];
|
||||||
|
this.designer = this.first.document.designer;
|
||||||
|
|
||||||
|
// setups
|
||||||
|
this.setupComponentMeta();
|
||||||
|
|
||||||
|
// clear fields
|
||||||
|
this.setupItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupComponentMeta() {
|
||||||
|
// todo: enhance compile a temp configure.compiled
|
||||||
|
const first = this.first;
|
||||||
|
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() {
|
||||||
|
if (this.componentMeta) {
|
||||||
|
this._items = this.componentMeta.configure.map((item) => {
|
||||||
|
if (isCustomView(item)) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
return new SettingField(this, item as any);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前属性值
|
||||||
|
*/
|
||||||
|
@computed getValue(): any {
|
||||||
|
this.first.propsData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置当前属性值
|
||||||
|
*/
|
||||||
|
setValue(val: any) {
|
||||||
|
this.setProps(val);
|
||||||
|
// TODO: emit value change
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取子项
|
||||||
|
*/
|
||||||
|
get(propName: string | number): SettingPropEntry {
|
||||||
|
return new SettingPropEntry(this, propName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置子级属性值
|
||||||
|
*/
|
||||||
|
setPropValue(propName: string, value: any) {
|
||||||
|
this.nodes.forEach((node) => {
|
||||||
|
node.setPropValue(propName, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取子级属性值
|
||||||
|
*/
|
||||||
|
getPropValue(propName: string): any {
|
||||||
|
return this.first.getProp(propName, true)?.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取兄弟项
|
||||||
|
*/
|
||||||
|
getSibling(propName: string | number) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取得兄弟属性值
|
||||||
|
*/
|
||||||
|
getSiblingValue(propName: string | number): any {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置兄弟属性值
|
||||||
|
*/
|
||||||
|
setSiblingValue(propName: string | number, value: any): void {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取顶层附属属性值
|
||||||
|
*/
|
||||||
|
getExtraPropValue(propName: string) {
|
||||||
|
return this.first.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 disposeItems() {
|
||||||
|
this._items.forEach((item) => isPurgeable(item) && item.purge());
|
||||||
|
this._items = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
purge() {
|
||||||
|
this.disposeItems();
|
||||||
|
this.emitter.removeAllListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==== compatibles for vision =====
|
||||||
|
getProp(propName: string | number) {
|
||||||
|
return this.get(propName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Purgeable {
|
||||||
|
purge(): void;
|
||||||
|
}
|
||||||
|
export function isPurgeable(obj: any): obj is Purgeable {
|
||||||
|
return obj && obj.purge;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class SettingPropEntry implements SettingTarget {
|
||||||
|
// === static properties ===
|
||||||
|
readonly editor: Editor;
|
||||||
|
readonly isSameComponent: boolean;
|
||||||
|
readonly isMultiNodes: boolean;
|
||||||
|
readonly isOneNode: boolean;
|
||||||
|
readonly nodes: Node[];
|
||||||
|
readonly componentMeta: ComponentMeta | null;
|
||||||
|
readonly designer: Designer;
|
||||||
|
readonly top: SettingTarget;
|
||||||
|
readonly isGroup: boolean;
|
||||||
|
readonly type: 'field' | 'group';
|
||||||
|
readonly id = uniqueId('entry');
|
||||||
|
|
||||||
|
// ==== dynamic properties ====
|
||||||
|
@obx.ref private _name: string | number;
|
||||||
|
get name() {
|
||||||
|
return this._name;
|
||||||
|
}
|
||||||
|
@computed get path() {
|
||||||
|
const path = this.parent.path.slice();
|
||||||
|
if (this.type === 'field') {
|
||||||
|
path.push(this.name);
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
extraProps: any = {};
|
||||||
|
|
||||||
|
constructor(readonly parent: SettingTarget, name: string | number, type?: 'field' | 'group') {
|
||||||
|
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;
|
||||||
|
this.isGroup = this.type === 'group';
|
||||||
|
|
||||||
|
// copy parent static properties
|
||||||
|
this.editor = parent.editor;
|
||||||
|
this.nodes = parent.nodes;
|
||||||
|
this.componentMeta = parent.componentMeta;
|
||||||
|
this.isSameComponent = parent.isSameComponent;
|
||||||
|
this.isMultiNodes = parent.isMultiNodes;
|
||||||
|
this.isOneNode = parent.isOneNode;
|
||||||
|
this.designer = parent.designer;
|
||||||
|
this.top = parent.top;
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====== 当前属性读写 =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前属性值
|
||||||
|
*/
|
||||||
|
@computed 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);
|
||||||
|
}
|
||||||
|
// TODO: emit value change
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取子项
|
||||||
|
*/
|
||||||
|
get(propName: string | number) {
|
||||||
|
const path = this.path.concat(propName).join('.');
|
||||||
|
return this.top.get(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置子级属性值
|
||||||
|
*/
|
||||||
|
setPropValue(propName: string | number, value: any) {
|
||||||
|
const path = this.path.concat(propName).join('.');
|
||||||
|
this.top.setPropValue(path, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取子级属性值
|
||||||
|
*/
|
||||||
|
getPropValue(propName: string | number): any {
|
||||||
|
return this.top.getPropValue(this.path.concat(propName).join('.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取兄弟项
|
||||||
|
*/
|
||||||
|
getSibling(propName: string | number) {
|
||||||
|
return this.parent.get(propName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取得兄弟属性值
|
||||||
|
*/
|
||||||
|
getSiblingValue(propName: string | number): any {
|
||||||
|
return this.parent.getPropValue(propName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置兄弟属性值
|
||||||
|
*/
|
||||||
|
setSiblingValue(propName: string | number, value: any): void {
|
||||||
|
this.parent.setPropValue(propName, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取顶层附属属性值
|
||||||
|
*/
|
||||||
|
getExtraPropValue(propName: string) {
|
||||||
|
return this.top.getExtraPropValue(propName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置顶层附属属性值
|
||||||
|
*/
|
||||||
|
setExtraPropValue(propName: string, value: any) {
|
||||||
|
this.top.setExtraPropValue(propName, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======= compatibles for vision ======
|
||||||
|
getNode() {
|
||||||
|
return this.nodes[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
getProps() {
|
||||||
|
return this.top;
|
||||||
|
}
|
||||||
|
|
||||||
|
onValueChange() {
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
|
}
|
||||||
123
packages/plugin-settings-pane/src/settings/setting-field.ts
Normal file
123
packages/plugin-settings-pane/src/settings/setting-field.ts
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import { TitleContent, computed, isDynamicSetter, SetterType, DynamicSetter, FieldExtraProps, FieldConfig, CustomView, isCustomView } from '@ali/lowcode-globals';
|
||||||
|
import { Transducer } from '../utils';
|
||||||
|
import { SettingPropEntry } from './setting-entry';
|
||||||
|
import { SettingTarget } from './setting-target';
|
||||||
|
|
||||||
|
export class SettingField extends SettingPropEntry implements SettingTarget {
|
||||||
|
readonly isSettingField = true;
|
||||||
|
readonly isRequired: boolean;
|
||||||
|
readonly transducer: Transducer;
|
||||||
|
extraProps: FieldExtraProps;
|
||||||
|
|
||||||
|
// ==== dynamic properties ====
|
||||||
|
private _title?: TitleContent;
|
||||||
|
get title() {
|
||||||
|
// FIXME! intl
|
||||||
|
return this._title || (typeof this.name === 'number' ? `项目 ${this.name}` : this.name);
|
||||||
|
}
|
||||||
|
private _setter?: SetterType | DynamicSetter;
|
||||||
|
@computed get setter(): SetterType | null {
|
||||||
|
if (!this._setter) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (isDynamicSetter(this._setter)) {
|
||||||
|
return this._setter(this);
|
||||||
|
}
|
||||||
|
return this._setter;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(readonly parent: SettingTarget, config: FieldConfig) {
|
||||||
|
super(parent, config.name, config.type);
|
||||||
|
|
||||||
|
const { title, items, setter, extraProps, ...rest } = config;
|
||||||
|
this._title = title;
|
||||||
|
this._setter = setter;
|
||||||
|
this.extraProps = {
|
||||||
|
...rest,
|
||||||
|
...extraProps,
|
||||||
|
};
|
||||||
|
this.isRequired = config.isRequired || (setter as any)?.isRequired;
|
||||||
|
|
||||||
|
// initial items
|
||||||
|
if (this.type === 'group' && items) {
|
||||||
|
this.initItems(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.transducer = new Transducer(this, { setter });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _items: Array<SettingField | CustomView> = [];
|
||||||
|
|
||||||
|
get items(): Array<SettingField | CustomView> {
|
||||||
|
return this._items;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建子配置项,通常用于 object/array 类型数据
|
||||||
|
createField(config: FieldConfig): SettingField {
|
||||||
|
return new SettingField(this, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====== 当前属性读写 =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断当前属性值是否一致
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
purge() {
|
||||||
|
this.disposeItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======= compatibles for vision ======
|
||||||
|
getHotValue(): any {
|
||||||
|
return this.transducer.toHot(this.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
setHotValue(data: any) {
|
||||||
|
this.setValue(this.transducer.toNative(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
onEffect(action: () => void): () => void {
|
||||||
|
return this.designer.autorun(action, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isSettingField(obj: any): obj is SettingField {
|
||||||
|
return obj && obj.isSettingField;
|
||||||
|
}
|
||||||
70
packages/plugin-settings-pane/src/settings/setting-target.ts
Normal file
70
packages/plugin-settings-pane/src/settings/setting-target.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import { ComponentMeta, Designer, Node } from '@ali/lowcode-designer';
|
||||||
|
import Editor from '@ali/lowcode-editor-core';
|
||||||
|
|
||||||
|
export interface SettingTarget {
|
||||||
|
|
||||||
|
readonly nodes: Node[];
|
||||||
|
|
||||||
|
readonly componentMeta: ComponentMeta | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同样类型的节点
|
||||||
|
*/
|
||||||
|
readonly isSameComponent: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一个
|
||||||
|
*/
|
||||||
|
readonly isOneNode: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 多个
|
||||||
|
*/
|
||||||
|
readonly isMultiNodes: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑器引用
|
||||||
|
*/
|
||||||
|
readonly editor: Editor;
|
||||||
|
|
||||||
|
readonly designer: Designer;
|
||||||
|
|
||||||
|
readonly path: Array<string| number>;
|
||||||
|
|
||||||
|
// 顶端对应 Props
|
||||||
|
readonly top: SettingTarget;
|
||||||
|
|
||||||
|
// 父级
|
||||||
|
readonly parent: SettingTarget;
|
||||||
|
|
||||||
|
|
||||||
|
// 获取当前值
|
||||||
|
getValue(): any;
|
||||||
|
|
||||||
|
// 设置当前值
|
||||||
|
setValue(value: any): void;
|
||||||
|
|
||||||
|
// 取得子项
|
||||||
|
get(propName: string | number): SettingTarget;
|
||||||
|
|
||||||
|
// 获取子项属性值
|
||||||
|
getPropValue(propName: string | number): any;
|
||||||
|
|
||||||
|
// 设置子项属性值
|
||||||
|
setPropValue(propName: string | number, value: any): void;
|
||||||
|
|
||||||
|
// 取得兄弟项
|
||||||
|
getSibling(propName: string | number): SettingTarget | null;
|
||||||
|
|
||||||
|
// 取得兄弟属性值
|
||||||
|
getSiblingValue(propName: string | number): any;
|
||||||
|
|
||||||
|
// 设置兄弟属性值
|
||||||
|
setSiblingValue(propName: string | number, value: any): void;
|
||||||
|
|
||||||
|
// 获取顶层附属属性值
|
||||||
|
getExtraPropValue(propName: string): any;
|
||||||
|
|
||||||
|
// 设置顶层附属属性值
|
||||||
|
setExtraPropValue(propName: string, value: any): void;
|
||||||
|
}
|
||||||
146
packages/plugin-settings-pane/src/settings/settings-pane.tsx
Normal file
146
packages/plugin-settings-pane/src/settings/settings-pane.tsx
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import { Component } from 'react';
|
||||||
|
import {
|
||||||
|
createContent,
|
||||||
|
CustomView,
|
||||||
|
intl,
|
||||||
|
shallowIntl,
|
||||||
|
isSetterConfig,
|
||||||
|
createSetterContent,
|
||||||
|
observer,
|
||||||
|
} from '@ali/lowcode-globals';
|
||||||
|
import { Field, FieldGroup } from '../field';
|
||||||
|
import PopupService from '../popup';
|
||||||
|
import { SettingField, isSettingField } from './setting-field';
|
||||||
|
import { SettingTarget } from './setting-target';
|
||||||
|
import { SettingTopEntry } from './setting-entry';
|
||||||
|
|
||||||
|
@observer
|
||||||
|
class SettingFieldView extends Component<{ field: SettingField }> {
|
||||||
|
render() {
|
||||||
|
const { field } = this.props;
|
||||||
|
const { extraProps } = field;
|
||||||
|
const { condition, defaultValue } = extraProps;
|
||||||
|
const visible = field.isOneNode && typeof condition === 'function' ? !condition(field) : true;
|
||||||
|
if (!visible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { setter } = field;
|
||||||
|
let setterProps: any = {};
|
||||||
|
let setterType: any;
|
||||||
|
if (Array.isArray(setter)) {
|
||||||
|
setterType = 'MixedSetter';
|
||||||
|
setterProps = {
|
||||||
|
setters: setter,
|
||||||
|
};
|
||||||
|
} else if (isSetterConfig(setter)) {
|
||||||
|
setterType = setter.componentName;
|
||||||
|
if (setter.props) {
|
||||||
|
setterProps = setter.props;
|
||||||
|
if (typeof setterProps === 'function') {
|
||||||
|
setterProps = setterProps(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (setter) {
|
||||||
|
setterType = setter;
|
||||||
|
}
|
||||||
|
let value = null;
|
||||||
|
if (field.type === 'field') {
|
||||||
|
if (defaultValue != null && !('defaultValue' in setterProps)) {
|
||||||
|
setterProps.defaultValue = defaultValue;
|
||||||
|
}
|
||||||
|
if (field.valueState > 0) {
|
||||||
|
value = field.getValue();
|
||||||
|
} else {
|
||||||
|
setterProps.multiValue = true;
|
||||||
|
if (!('placeholder' in setterProps)) {
|
||||||
|
// FIXME! move to locale file
|
||||||
|
setterProps.placeholder = intl({
|
||||||
|
type: 'i18n',
|
||||||
|
'zh-CN': '多种值',
|
||||||
|
'en-US': 'Multiple Value',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// todo: error handling
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Field title={extraProps.forceInline ? null : field.title}>
|
||||||
|
{createSetterContent(setterType, {
|
||||||
|
...shallowIntl(setterProps),
|
||||||
|
forceInline: extraProps.forceInline,
|
||||||
|
key: field.id,
|
||||||
|
// === injection
|
||||||
|
prop: field, // for compatible vision
|
||||||
|
field,
|
||||||
|
// === IO
|
||||||
|
value, // reaction point
|
||||||
|
onChange: (value: any) => {
|
||||||
|
this.setState({
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
field.setValue(value);
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
</Field>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@observer
|
||||||
|
class SettingGroupView extends Component<{ field: SettingField }> {
|
||||||
|
shouldComponentUpdate() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { field } = this.props;
|
||||||
|
const { extraProps } = field;
|
||||||
|
const { condition, defaultCollapsed } = extraProps;
|
||||||
|
const visible = field.isOneNode && typeof condition === 'function' ? !condition(field) : true;
|
||||||
|
|
||||||
|
if (!visible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FieldGroup title={field.title} defaultCollapsed={defaultCollapsed}>
|
||||||
|
{field.items.map((item, index) => createSettingFieldView(item, field, index))}
|
||||||
|
</FieldGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createSettingFieldView(item: SettingField | CustomView, field: SettingTarget, index?: number) {
|
||||||
|
if (isSettingField(item)) {
|
||||||
|
if (item.isGroup) {
|
||||||
|
return <SettingGroupView field={item} key={item.id} />;
|
||||||
|
} else {
|
||||||
|
return <SettingFieldView field={item} key={item.id} />;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return createContent(item, { key: index, field });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export default class SettingsPane extends Component<{ target: SettingTopEntry | SettingField }> {
|
||||||
|
shouldComponentUpdate() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { target } = this.props;
|
||||||
|
const items = target.items
|
||||||
|
return (
|
||||||
|
<div className="lc-settings-pane">
|
||||||
|
{/* todo: add head for single use */}
|
||||||
|
<PopupService>
|
||||||
|
<div className="lc-settings-content">
|
||||||
|
{items.map((item, index) => createSettingFieldView(item, target, index))}
|
||||||
|
</div>
|
||||||
|
</PopupService>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { TransformedComponentMetadata, FieldConfig } from '@ali/lowcode-globals';
|
import { TransformedComponentMetadata, FieldConfig } from '@ali/lowcode-globals';
|
||||||
import { SettingField } from '../main';
|
import { SettingField } from '../settings/setting-field';
|
||||||
|
|
||||||
export default function(metadata: TransformedComponentMetadata): TransformedComponentMetadata {
|
export default function(metadata: TransformedComponentMetadata): TransformedComponentMetadata {
|
||||||
const { componentName, configure = {} } = metadata;
|
const { componentName, configure = {} } = metadata;
|
||||||
|
|||||||
@ -1,21 +1,21 @@
|
|||||||
{
|
{
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"packages": {
|
"packages": [
|
||||||
"moment": {
|
{
|
||||||
"package": "moment",
|
"package": "moment",
|
||||||
"urls": ["https://g.alicdn.com/mylib/moment/2.24.0/min/moment.min.js"],
|
"urls": ["https://g.alicdn.com/mylib/moment/2.24.0/min/moment.min.js"],
|
||||||
"library": "moment"
|
"library": "moment"
|
||||||
},
|
},
|
||||||
"@alifd/next": {
|
{
|
||||||
"title": "fusion组件库",
|
"title": "fusion组件库",
|
||||||
"package": "@alifd/next",
|
"package": "@alifd/next",
|
||||||
"version": "1.19.18",
|
"version": "1.19.18",
|
||||||
"urls": ["https://unpkg.antfin-inc.com/@alife/next@1.19.18/dist/next.js", "https://unpkg.antfin-inc.com/@alife/next@1.19.18/dist/next.css"],
|
"urls": ["https://unpkg.antfin-inc.com/@alife/next@1.19.18/dist/next.js", "https://unpkg.antfin-inc.com/@alife/next@1.19.18/dist/next.css"],
|
||||||
"library": "Next"
|
"library": "Next"
|
||||||
}
|
}
|
||||||
},
|
],
|
||||||
"components": {
|
"components": [
|
||||||
"Page": {
|
{
|
||||||
"componentName": "Page",
|
"componentName": "Page",
|
||||||
"title": "页面",
|
"title": "页面",
|
||||||
"configure": {
|
"configure": {
|
||||||
@ -39,7 +39,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Div": {
|
{
|
||||||
"componentName": "Div",
|
"componentName": "Div",
|
||||||
"title": "容器",
|
"title": "容器",
|
||||||
"configure": {
|
"configure": {
|
||||||
@ -48,7 +48,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Button": {
|
{
|
||||||
"componentName": "Button",
|
"componentName": "Button",
|
||||||
"title": "按钮",
|
"title": "按钮",
|
||||||
"devMode": "proCode",
|
"devMode": "proCode",
|
||||||
@ -147,7 +147,7 @@
|
|||||||
"propType": "node"
|
"propType": "node"
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
"Button.Group": {
|
{
|
||||||
"componentName": "Button.Group",
|
"componentName": "Button.Group",
|
||||||
"title": "按钮组",
|
"title": "按钮组",
|
||||||
"devMode": "proCode",
|
"devMode": "proCode",
|
||||||
@ -186,7 +186,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Input": {
|
{
|
||||||
"componentName": "Input",
|
"componentName": "Input",
|
||||||
"title": "输入框",
|
"title": "输入框",
|
||||||
"devMode": "proCode",
|
"devMode": "proCode",
|
||||||
@ -300,7 +300,7 @@
|
|||||||
"description": "预览态模式下渲染的内容\n@param {number} value 评分值"
|
"description": "预览态模式下渲染的内容\n@param {number} value 评分值"
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
"Form": {
|
{
|
||||||
"componentName": "Form",
|
"componentName": "Form",
|
||||||
"title": "表单容器",
|
"title": "表单容器",
|
||||||
"devMode": "proCode",
|
"devMode": "proCode",
|
||||||
@ -423,7 +423,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Form.Item": {
|
{
|
||||||
"componentName": "Form.Item",
|
"componentName": "Form.Item",
|
||||||
"title": "表单项",
|
"title": "表单项",
|
||||||
"devMode": "proCode",
|
"devMode": "proCode",
|
||||||
@ -677,7 +677,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"NumberPicker": {
|
{
|
||||||
"componentName": "NumberPicker",
|
"componentName": "NumberPicker",
|
||||||
"title": "数字输入",
|
"title": "数字输入",
|
||||||
"devMode": "proCode",
|
"devMode": "proCode",
|
||||||
@ -829,7 +829,7 @@
|
|||||||
"description": "预设屏幕宽度"
|
"description": "预设屏幕宽度"
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
"Select": {
|
{
|
||||||
"componentName": "Select",
|
"componentName": "Select",
|
||||||
"title": "下拉",
|
"title": "下拉",
|
||||||
"devMode": "proCode",
|
"devMode": "proCode",
|
||||||
@ -1450,7 +1450,7 @@
|
|||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Select.Option": {
|
{
|
||||||
"componentName": "Select.Option",
|
"componentName": "Select.Option",
|
||||||
"title": "选择项",
|
"title": "选择项",
|
||||||
"devMode": "proCode",
|
"devMode": "proCode",
|
||||||
@ -1485,7 +1485,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
],
|
||||||
"componentList": [{
|
"componentList": [{
|
||||||
"title": "基础",
|
"title": "基础",
|
||||||
"icon": "",
|
"icon": "",
|
||||||
|
|||||||
@ -16,8 +16,7 @@ async function load() {
|
|||||||
|
|
||||||
const externals = ['react', 'react-dom', 'prop-types', 'react-router', 'react-router-dom', '@ali/recore'];
|
const externals = ['react', 'react-dom', 'prop-types', 'react-router', 'react-router-dom', '@ali/recore'];
|
||||||
async function loadAssets() {
|
async function loadAssets() {
|
||||||
const assets = await editor.utils.get('./legao-assets.json');
|
const assets = await editor.utils.get('./assets.json');
|
||||||
// Trunk.setPackages(assets.packages);
|
|
||||||
|
|
||||||
if (assets.packages) {
|
if (assets.packages) {
|
||||||
assets.packages.forEach((item: any) => {
|
assets.packages.forEach((item: any) => {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user