mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-21 10:10:24 +00:00
235 lines
5.2 KiB
TypeScript
235 lines
5.2 KiB
TypeScript
import { TitleContent, isI18nData } from '@alilc/lowcode-types';
|
||
import { computed, obx, intl, makeObservable, action } from '@alilc/lowcode-editor-core';
|
||
import { Node, DocumentModel, isLocationChildrenDetail, LocationChildrenDetail, Designer } from '@alilc/lowcode-designer';
|
||
import { Tree } from './tree';
|
||
|
||
export default class TreeNode {
|
||
get id(): string {
|
||
return this.node.id;
|
||
}
|
||
|
||
/**
|
||
* 是否可以展开
|
||
*/
|
||
get expandable(): boolean {
|
||
if (this.locked) return false;
|
||
return this.hasChildren() || this.hasSlots() || this.dropDetail?.index != null;
|
||
}
|
||
|
||
/**
|
||
* 插入"线"位置信息
|
||
*/
|
||
@computed get dropDetail(): LocationChildrenDetail | undefined | null {
|
||
const loc = this.node.document.dropLocation;
|
||
return loc && this.isResponseDropping() && isLocationChildrenDetail(loc.detail) ? loc.detail : null;
|
||
}
|
||
|
||
@computed get depth(): number {
|
||
return this.node.zLevel;
|
||
}
|
||
|
||
isRoot(includeOriginalRoot = false) {
|
||
return this.tree.root === this || (includeOriginalRoot && this.tree.document.rootNode === this.node);
|
||
}
|
||
|
||
/**
|
||
* 是否是响应投放区
|
||
*/
|
||
isResponseDropping(): boolean {
|
||
const loc = this.node.document.dropLocation;
|
||
if (!loc) {
|
||
return false;
|
||
}
|
||
return loc.target === this.node;
|
||
}
|
||
|
||
isFocusingNode(): boolean {
|
||
const loc = this.node.document.dropLocation;
|
||
if (!loc) {
|
||
return false;
|
||
}
|
||
return (
|
||
isLocationChildrenDetail(loc.detail) && loc.detail.focus?.type === 'node' && loc.detail.focus.node === this.node
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 默认为折叠状态
|
||
* 在初始化根节点时,设置为展开状态
|
||
*/
|
||
@obx.ref private _expanded = false;
|
||
|
||
get expanded(): boolean {
|
||
return this.isRoot(true) || (this.expandable && this._expanded);
|
||
}
|
||
|
||
setExpanded(value: boolean) {
|
||
this._expanded = value;
|
||
}
|
||
|
||
@computed get detecting() {
|
||
return this.designer.detecting.current === this.node;
|
||
}
|
||
|
||
@computed get hidden(): boolean {
|
||
const cv = this.node.isConditionalVisible();
|
||
if (cv == null) {
|
||
return !this.node.getVisible();
|
||
}
|
||
return !cv;
|
||
}
|
||
|
||
setHidden(flag: boolean) {
|
||
if (this.node.conditionGroup) {
|
||
return;
|
||
}
|
||
this.node.setVisible(!flag);
|
||
}
|
||
|
||
@computed get locked(): boolean {
|
||
return this.node.isLocked;
|
||
}
|
||
|
||
setLocked(flag: boolean) {
|
||
this.node.lock(flag);
|
||
}
|
||
|
||
@computed get selected(): boolean {
|
||
// TODO: check is dragging
|
||
const { selection } = this.document;
|
||
return selection.has(this.node.id);
|
||
}
|
||
|
||
@computed get title(): TitleContent {
|
||
return this.node.title;
|
||
}
|
||
|
||
@computed get titleLabel() {
|
||
let { title } = this;
|
||
if (!title) {
|
||
return '';
|
||
}
|
||
if ((title as any).label) {
|
||
title = (title as any).label;
|
||
}
|
||
if (typeof title === 'string') {
|
||
return title;
|
||
}
|
||
if (isI18nData(title)) {
|
||
return intl(title) as string;
|
||
}
|
||
return this.node.componentName;
|
||
}
|
||
|
||
setTitleLabel(label: string) {
|
||
const origLabel = this.titleLabel;
|
||
if (label === origLabel) {
|
||
return;
|
||
}
|
||
if (label === '') {
|
||
this.node.getExtraProp('title', false)?.remove();
|
||
} else {
|
||
this.node.getExtraProp('title', true)?.setValue(label);
|
||
}
|
||
}
|
||
|
||
get icon() {
|
||
return this.node.componentMeta.icon;
|
||
}
|
||
|
||
@computed get parent(): TreeNode | null {
|
||
const { parent } = this.node;
|
||
if (parent) {
|
||
return this.tree.getTreeNode(parent);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
@computed get slots(): TreeNode[] {
|
||
// todo: shallowEqual
|
||
return this.node.slots.map((node) => this.tree.getTreeNode(node));
|
||
}
|
||
|
||
@computed get children(): TreeNode[] | null {
|
||
return this.node.children?.map((node) => this.tree.getTreeNode(node)) || null;
|
||
}
|
||
|
||
/**
|
||
* 是否是容器,允许子节点拖入
|
||
*/
|
||
isContainer(): boolean {
|
||
return this.node.isContainer();
|
||
}
|
||
|
||
/**
|
||
* 判断是否有"插槽"
|
||
*/
|
||
hasSlots(): boolean {
|
||
return this.node.hasSlots();
|
||
}
|
||
|
||
hasChildren(): boolean {
|
||
return !!(this.isContainer() && this.node.children?.notEmpty());
|
||
}
|
||
|
||
select(isMulti: boolean) {
|
||
const { node } = this;
|
||
|
||
const { selection } = node.document;
|
||
if (isMulti) {
|
||
selection.add(node.id);
|
||
} else {
|
||
selection.select(node.id);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 展开节点,支持依次展开父节点
|
||
*/
|
||
expand(tryExpandParents = false) {
|
||
// 这边不能直接使用 expanded,需要额外判断是否可以展开
|
||
// 如果只使用 expanded,会漏掉不可以展开的情况,即在不可以展开的情况下,会触发展开
|
||
if (this.expandable && !this._expanded) {
|
||
this.setExpanded(true);
|
||
}
|
||
if (tryExpandParents) {
|
||
this.expandParents();
|
||
}
|
||
}
|
||
|
||
expandParents() {
|
||
let p = this.node.parent;
|
||
while (p) {
|
||
this.tree.getTreeNode(p).setExpanded(true);
|
||
p = p.parent;
|
||
}
|
||
}
|
||
|
||
readonly designer: Designer;
|
||
|
||
readonly document: DocumentModel;
|
||
|
||
@obx.ref private _node: Node;
|
||
|
||
get node() {
|
||
return this._node;
|
||
}
|
||
|
||
readonly tree: Tree;
|
||
|
||
constructor(tree: Tree, node: Node) {
|
||
makeObservable(this);
|
||
this.tree = tree;
|
||
this.document = node.document;
|
||
this.designer = this.document.designer;
|
||
this._node = node;
|
||
}
|
||
|
||
@action
|
||
setNode(node: Node) {
|
||
if (this._node !== node) {
|
||
this._node = node;
|
||
}
|
||
}
|
||
}
|