import { IPublicTypeTitleContent, IPublicTypeLocationChildrenDetail, IPublicModelNode, IPublicTypeDisposable, } from '@alilc/lowcode-types'; import { isI18nData, isLocationChildrenDetail } from '@alilc/lowcode-utils'; import EventEmitter from 'events'; import { Tree } from './tree'; import { IOutlinePanelPluginContext } from './tree-master'; /** * 大纲树过滤结果 */ export interface FilterResult { // 过滤条件是否生效 filterWorking: boolean; // 命中子节点 matchChild: boolean; // 命中本节点 matchSelf: boolean; // 关键字 keywords: string; } enum EVENT_NAMES { filterResultChanged = 'filterResultChanged', expandedChanged = 'expandedChanged', hiddenChanged = 'hiddenChanged', lockedChanged = 'lockedChanged', titleLabelChanged = 'titleLabelChanged', expandableChanged = 'expandableChanged', conditionChanged = 'conditionChanged', } export default class TreeNode { readonly pluginContext: IOutlinePanelPluginContext; event = new EventEmitter(); private _node: IPublicModelNode; readonly tree: Tree; private _filterResult: FilterResult = { filterWorking: false, matchChild: false, matchSelf: false, keywords: '', }; /** * 默认为折叠状态 * 在初始化根节点时,设置为展开状态 */ private _expanded = false; get id(): string { return this.node.id; } /** * 是否可以展开 */ get expandable(): boolean { if (this.locked) return false; return this.hasChildren() || this.hasSlots() || this.dropDetail?.index != null; } get expanded(): boolean { return this.isRoot(true) || (this.expandable && this._expanded); } /** * 插入"线"位置信息 */ get dropDetail(): IPublicTypeLocationChildrenDetail | undefined | null { const loc = this.pluginContext.project.getCurrentDocument()?.dropLocation; return loc && this.isResponseDropping() && isLocationChildrenDetail(loc.detail) ? loc.detail : null; } get depth(): number { return this.node.zLevel; } get detecting() { const doc = this.pluginContext.project.currentDocument; return !!(doc?.isDetectingNode(this.node)); } get hidden(): boolean { const cv = this.node.isConditionalVisible(); if (cv == null) { return !this.node.visible; } return !cv; } get locked(): boolean { return this.node.isLocked; } get selected(): boolean { // TODO: check is dragging const selection = this.pluginContext.project.getCurrentDocument()?.selection; if (!selection) { return false; } return selection?.has(this.node.id); } get title(): IPublicTypeTitleContent { return this.node.title; } 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)) { const currentLocale = this.pluginContext.getLocale(); const currentTitle = title[currentLocale]; return currentTitle; } return this.node.componentName; } get icon() { return this.node.componentMeta?.icon; } get parent(): TreeNode | null { const { parent } = this.node; if (parent) { return this.tree.getTreeNode(parent); } return null; } get slots(): TreeNode[] { // todo: shallowEqual return this.node.slots.map((node) => this.tree.getTreeNode(node)); } get condition(): boolean { return this.node.hasCondition() && !this.node.conditionGroup; } get children(): TreeNode[] | null { return this.node.children?.map((node) => this.tree.getTreeNode(node)) || null; } get node(): IPublicModelNode { return this._node; } constructor(tree: Tree, node: IPublicModelNode) { this.tree = tree; this.pluginContext = tree.pluginContext; this._node = node; } setLocked(flag: boolean) { this.node.lock(flag); this.event.emit(EVENT_NAMES.lockedChanged, flag); } onFilterResultChanged(fn: () => void): IPublicTypeDisposable { this.event.on(EVENT_NAMES.filterResultChanged, fn); return () => { this.event.off(EVENT_NAMES.filterResultChanged, fn); }; } onExpandedChanged(fn: (expanded: boolean) => void): IPublicTypeDisposable { this.event.on(EVENT_NAMES.expandedChanged, fn); return () => { this.event.off(EVENT_NAMES.expandedChanged, fn); }; } onHiddenChanged(fn: (hidden: boolean) => void): IPublicTypeDisposable { this.event.on(EVENT_NAMES.hiddenChanged, fn); return () => { this.event.off(EVENT_NAMES.hiddenChanged, fn); }; } onLockedChanged(fn: (locked: boolean) => void): IPublicTypeDisposable { this.event.on(EVENT_NAMES.lockedChanged, fn); return () => { this.event.off(EVENT_NAMES.lockedChanged, fn); }; } onTitleLabelChanged(fn: (treeNode: TreeNode) => void): IPublicTypeDisposable { this.event.on(EVENT_NAMES.titleLabelChanged, fn); return () => { this.event.off(EVENT_NAMES.titleLabelChanged, fn); }; } onConditionChanged(fn: (treeNode: TreeNode) => void): IPublicTypeDisposable { this.event.on(EVENT_NAMES.conditionChanged, fn); return () => { this.event.off(EVENT_NAMES.conditionChanged, fn); }; } onExpandableChanged(fn: (expandable: boolean) => void): IPublicTypeDisposable { this.event.on(EVENT_NAMES.expandableChanged, fn); return () => { this.event.off(EVENT_NAMES.expandableChanged, fn); }; } /** * 触发 onExpandableChanged 回调 */ notifyExpandableChanged(): void { this.event.emit(EVENT_NAMES.expandableChanged, this.expandable); } notifyTitleLabelChanged(): void { this.event.emit(EVENT_NAMES.titleLabelChanged, this.title); } notifyConditionChanged(): void { this.event.emit(EVENT_NAMES.conditionChanged, this.condition); } setHidden(flag: boolean) { if (this.node.conditionGroup) { return; } if (this.node.visible !== !flag) { this.node.visible = !flag; } this.event.emit(EVENT_NAMES.hiddenChanged, flag); } isFocusingNode(): boolean { const loc = this.pluginContext.project.getCurrentDocument()?.dropLocation; if (!loc) { return false; } return ( isLocationChildrenDetail(loc.detail) && loc.detail.focus?.type === 'node' && loc.detail?.focus?.node.id === this.id ); } setExpanded(value: boolean) { this._expanded = value; this.event.emit(EVENT_NAMES.expandedChanged, value); } isRoot(includeOriginalRoot = false) { const rootNode = this.pluginContext.project.getCurrentDocument()?.root; return this.tree.root === this || (includeOriginalRoot && rootNode === this.node); } /** * 是否是响应投放区 */ isResponseDropping(): boolean { const loc = this.pluginContext.project.getCurrentDocument()?.dropLocation; if (!loc) { return false; } return loc.target?.id === this.id; } 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); } this.event.emit(EVENT_NAMES.titleLabelChanged, this); } /** * 是否是容器,允许子节点拖入 */ isContainer(): boolean { return this.node.isContainerNode; } /** * 判断是否有"插槽" */ hasSlots(): boolean { return this.node.hasSlots(); } hasChildren(): boolean { return !!(this.isContainer() && this.node.children?.notEmptyNode); } select(isMulti: boolean) { const { node } = this; const selection = this.pluginContext.project.getCurrentDocument()?.selection; 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; } } setNode(node: IPublicModelNode) { if (this._node !== node) { this._node = node; } } get filterReult(): FilterResult { return this._filterResult; } setFilterReult(val: FilterResult) { this._filterResult = val; this.event.emit(EVENT_NAMES.filterResultChanged); } }