mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-01-13 09:41:57 +00:00
Merge branch 'feat/support-model-node-in-outline-pane' into 'release/0.9.0'
Feat/support model node in outline pane 大纲树支持模态视图 模态视图只能在根节点 See merge request !893801
This commit is contained in:
commit
d282a37542
@ -826,10 +826,14 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
event: e,
|
||||
};
|
||||
|
||||
if (e.dragObject.nodes[0].getPrototype().isModal()) {
|
||||
if (e.dragObject.type === 'node' && e.dragObject.nodes[0].getPrototype().isModal()) {
|
||||
return this.designer.createLocation({
|
||||
target: this.document.rootNode,
|
||||
detail,
|
||||
detail: {
|
||||
type: LocationDetailType.Children,
|
||||
index: 0,
|
||||
valid: true,
|
||||
},
|
||||
source: 'simulator' + this.document.id,
|
||||
event: e,
|
||||
});
|
||||
|
||||
@ -10,7 +10,7 @@ import { Selection } from './selection';
|
||||
import { History } from './history';
|
||||
import { TransformStage } from './node';
|
||||
import { uniqueId } from '@ali/lowcode-utils';
|
||||
import ModalNodesManager from './node/modalNodesManager';
|
||||
import { ModalNodesManager } from './node';
|
||||
|
||||
export type GetDataType<T, NodeType> = T extends undefined
|
||||
? NodeType extends {
|
||||
@ -37,6 +37,10 @@ export class DocumentModel {
|
||||
* 操作记录控制
|
||||
*/
|
||||
readonly history: History;
|
||||
/**
|
||||
* 模态节点管理
|
||||
*/
|
||||
readonly modalNodesManager: ModalNodesManager;
|
||||
|
||||
private nodesMap = new Map<string, Node>();
|
||||
@obx.val private nodes = new Set<Node>();
|
||||
@ -44,7 +48,6 @@ export class DocumentModel {
|
||||
private _simulator?: ISimulatorHost;
|
||||
private emitter: EventEmitter;
|
||||
private rootNodeVisitorMap: { [visitorName: string]: any } = {};
|
||||
private modalNodesManager: ModalNodesManager;
|
||||
|
||||
/**
|
||||
* 模拟器
|
||||
|
||||
@ -5,3 +5,4 @@ export * from './props/prop';
|
||||
export * from './props/prop-stash';
|
||||
export * from './props/props';
|
||||
export * from './transform-stage';
|
||||
export * from './modal-nodes-manager';
|
||||
|
||||
@ -17,11 +17,11 @@ function getModalNodes(node: Node) {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
export default class ModalNodesManager {
|
||||
export class ModalNodesManager {
|
||||
public willDestroy: any;
|
||||
|
||||
private page: DocumentModel;
|
||||
private modalNodes: [Node];
|
||||
private modalNodes: Node[];
|
||||
private nodeRemoveEvents: any;
|
||||
private emitter: EventEmitter;
|
||||
|
||||
@ -44,7 +44,7 @@ export default class ModalNodesManager {
|
||||
public getVisibleModalNode() {
|
||||
const visibleNode = this.modalNodes
|
||||
? this.modalNodes.find((node: Node) => {
|
||||
return !node.getExtraProp('hidden');
|
||||
return node.getVisible();
|
||||
})
|
||||
: null;
|
||||
return visibleNode;
|
||||
@ -53,18 +53,18 @@ export default class ModalNodesManager {
|
||||
public hideModalNodes() {
|
||||
if (this.modalNodes) {
|
||||
this.modalNodes.forEach((node: Node) => {
|
||||
node.getExtraProp('hidden')?.setValue(true);
|
||||
node.setVisible(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public setVisible(node: Node) {
|
||||
this.hideModalNodes();
|
||||
node.getExtraProp('hidden')?.setValue(false);
|
||||
node.setVisible(true);
|
||||
}
|
||||
|
||||
public setInvisible(node: Node) {
|
||||
node.getExtraProp('hidden')?.setValue(true);
|
||||
node.setVisible(false);
|
||||
}
|
||||
|
||||
public onVisibleChange(func: () => any) {
|
||||
@ -101,26 +101,24 @@ export default class ModalNodesManager {
|
||||
}
|
||||
this.removeNodeEvent(node);
|
||||
this.emitter.emit('modalNodesChange');
|
||||
if (!node.getExtraProp('hidden')) {
|
||||
if (node.getVisible()) {
|
||||
this.emitter.emit('visibleChange');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private addNodeEvent(node: Node) {
|
||||
// this.nodeRemoveEvents[node.getId()] =
|
||||
// node.onStatusChange((status: any, field: any) => {
|
||||
// if (field === 'visibility') {
|
||||
// this.emitter.emit('visibleChange');
|
||||
// }
|
||||
// });
|
||||
this.nodeRemoveEvents[node.getId()] =
|
||||
node.onVisibleChange((flag) => {
|
||||
this.emitter.emit('visibleChange');
|
||||
});
|
||||
}
|
||||
|
||||
private removeNodeEvent(node: Node) {
|
||||
// if (this.nodeRemoveEvents[node.getId()]) {
|
||||
// this.nodeRemoveEvents[node.getId()]();
|
||||
// delete this.nodeRemoveEvents[node.getId()];
|
||||
// }
|
||||
if (this.nodeRemoveEvents[node.getId()]) {
|
||||
this.nodeRemoveEvents[node.getId()]();
|
||||
delete this.nodeRemoveEvents[node.getId()];
|
||||
}
|
||||
}
|
||||
|
||||
private setNodes() {
|
||||
@ -22,6 +22,7 @@ import { ExclusiveGroup, isExclusiveGroup } from './exclusive-group';
|
||||
import { TransformStage } from './transform-stage';
|
||||
import { ReactElement } from 'react';
|
||||
import { SettingTopEntry } from 'designer/src/designer';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
/**
|
||||
* 基础节点
|
||||
@ -72,6 +73,7 @@ import { SettingTopEntry } from 'designer/src/designer';
|
||||
* hidden
|
||||
*/
|
||||
export class Node<Schema extends NodeSchema = NodeSchema> {
|
||||
private emitter: EventEmitter;
|
||||
/**
|
||||
* 是节点实例
|
||||
*/
|
||||
@ -162,6 +164,7 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
|
||||
}
|
||||
|
||||
this.settingEntry = this.document.designer.createSettingEntry([ this ]);
|
||||
this.emitter = new EventEmitter();
|
||||
}
|
||||
|
||||
private initProps(props: any): any {
|
||||
@ -415,6 +418,22 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
|
||||
return node;
|
||||
}
|
||||
|
||||
setVisible(flag: boolean): void {
|
||||
this.getExtraProp('hidden')?.setValue(!flag);
|
||||
this.emitter.emit('visibleChange', flag);
|
||||
}
|
||||
|
||||
getVisible(): boolean {
|
||||
return !this.getExtraProp('hidden', false)?.getValue();
|
||||
}
|
||||
|
||||
onVisibleChange(func: (flag: boolean) => any) {
|
||||
this.emitter.on('visibleChange', func);
|
||||
return () => {
|
||||
this.emitter.removeListener('visibleChange', func);
|
||||
};
|
||||
}
|
||||
|
||||
getProp(path: string, stash = true): Prop | null {
|
||||
return this.props.query(path, stash as any) || null;
|
||||
}
|
||||
|
||||
14
packages/plugin-outline-pane/src/icons/radio-active.tsx
Normal file
14
packages/plugin-outline-pane/src/icons/radio-active.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { SVGIcon, IconProps } from '@ali/lowcode-utils';
|
||||
|
||||
export function IconRadioActive(props: IconProps) {
|
||||
return (
|
||||
<SVGIcon viewBox="0 0 1024 1024" {...props}>
|
||||
<path d="M512 1024A512 512 0 1 1 512 0a512 512 0 0 1 0 1024z m0-256a256 256 0 1 0 0-512 256 256 0 0 0 0 512z"></path>
|
||||
</SVGIcon>
|
||||
);
|
||||
}
|
||||
|
||||
IconRadioActive.displayName = 'IconRadioActive';
|
||||
|
||||
|
||||
|
||||
14
packages/plugin-outline-pane/src/icons/radio.tsx
Normal file
14
packages/plugin-outline-pane/src/icons/radio.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { SVGIcon, IconProps } from '@ali/lowcode-utils';
|
||||
|
||||
export function IconRadio(props: IconProps) {
|
||||
return (
|
||||
<SVGIcon viewBox="0 0 1024 1024" {...props}>
|
||||
<path d="M512 1024A512 512 0 1 1 512 0a512 512 0 0 1 0 1024z m0-64A448 448 0 1 0 512 64a448 448 0 0 0 0 896z"></path>
|
||||
</SVGIcon>
|
||||
);
|
||||
}
|
||||
|
||||
IconRadio.displayName = 'IconRadio';
|
||||
|
||||
|
||||
|
||||
@ -134,6 +134,20 @@ export class OutlineMain implements ISensor, ITreeBoard, IScrollable {
|
||||
const pos = getPosFromEvent(e, this._shell);
|
||||
const irect = this.getInsertionRect();
|
||||
const originLoc = document.dropLocation;
|
||||
|
||||
if (e.dragObject.type === 'node' && e.dragObject.nodes[0].getPrototype().isModal()) {
|
||||
return designer.createLocation({
|
||||
target: document.rootNode,
|
||||
detail: {
|
||||
type: LocationDetailType.Children,
|
||||
index: 0,
|
||||
valid: true,
|
||||
},
|
||||
source: this.id,
|
||||
event: e,
|
||||
});
|
||||
}
|
||||
|
||||
if (originLoc && ((pos && pos === 'unchanged') || (irect && globalY >= irect.top && globalY <= irect.bottom))) {
|
||||
const loc = originLoc.clone(e);
|
||||
const indented = this.indentTrack.getIndentParent(originLoc, loc);
|
||||
|
||||
@ -72,7 +72,7 @@ export default class TreeNode {
|
||||
@computed get hidden(): boolean {
|
||||
const cv = this.node.isConditionalVisible();
|
||||
if (cv == null) {
|
||||
return this.node.getExtraProp('hidden', false)?.getValue() === true;
|
||||
return !this.node.getVisible();
|
||||
}
|
||||
return !cv;
|
||||
}
|
||||
@ -81,11 +81,7 @@ export default class TreeNode {
|
||||
if (this.node.conditionGroup) {
|
||||
return;
|
||||
}
|
||||
if (flag) {
|
||||
this.node.getExtraProp('hidden', true)?.setValue(true);
|
||||
} else {
|
||||
this.node.getExtraProp('hidden', false)?.remove();
|
||||
}
|
||||
this.node.setVisible(!flag);
|
||||
}
|
||||
|
||||
@computed get locked(): boolean {
|
||||
|
||||
85
packages/plugin-outline-pane/src/views/root-tree-node.tsx
Normal file
85
packages/plugin-outline-pane/src/views/root-tree-node.tsx
Normal file
@ -0,0 +1,85 @@
|
||||
import { Component } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { observer } from '@ali/lowcode-editor-core';
|
||||
import TreeNode from '../tree-node';
|
||||
import TreeTitle from './tree-title';
|
||||
import TreeBranches from './tree-branches';
|
||||
import { ModalNodesManager } from '@ali/lowcode-designer';
|
||||
import { IconEyeClose } from '../icons/eye-close';
|
||||
|
||||
@observer
|
||||
class ModalTreeNodeView extends Component<{ treeNode: TreeNode }> {
|
||||
private modalNodesManager: ModalNodesManager;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
|
||||
// 模态管理对象
|
||||
this.modalNodesManager = props.treeNode.document.modalNodesManager;
|
||||
}
|
||||
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
hideAllNodes() {
|
||||
this.modalNodesManager.hideModalNodes();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { treeNode } = this.props;
|
||||
const hasVisibleModalNode = !!this.modalNodesManager.getVisibleModalNode();
|
||||
return (
|
||||
<div className="tree-node-modal">
|
||||
<div className="tree-node-modal-title">
|
||||
<span>模态视图层</span>
|
||||
<div className="tree-node-modal-title-visible-icon"
|
||||
onClick={this.hideAllNodes.bind(this)}>
|
||||
{hasVisibleModalNode ? <IconEyeClose /> : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="tree-pane-modal-content">
|
||||
<TreeBranches treeNode={treeNode} isModal={true}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
export default class RootTreeNodeView extends Component<{ treeNode: TreeNode }> {
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { treeNode } = this.props;
|
||||
const className = classNames('tree-node', {
|
||||
// 是否展开
|
||||
expanded: treeNode.expanded,
|
||||
// 是否悬停中
|
||||
detecting: treeNode.detecting,
|
||||
// 是否选中的
|
||||
selected: treeNode.selected,
|
||||
// 是否隐藏的
|
||||
hidden: treeNode.hidden,
|
||||
// 是否忽略的
|
||||
// ignored: treeNode.ignored,
|
||||
// 是否锁定的
|
||||
locked: treeNode.locked,
|
||||
// 是否投放响应
|
||||
dropping: treeNode.dropDetail?.index != null,
|
||||
'is-root': treeNode.isRoot(),
|
||||
'condition-flow': treeNode.node.conditionGroup != null,
|
||||
highlight: treeNode.isFocusingNode(),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={className} data-id={treeNode.id}>
|
||||
<TreeTitle treeNode={treeNode} />
|
||||
<ModalTreeNodeView treeNode={treeNode} />
|
||||
<TreeBranches treeNode={treeNode}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -21,6 +21,46 @@
|
||||
margin-bottom: @treeNodeHeight;
|
||||
user-select: none;
|
||||
|
||||
.tree-node-modal {
|
||||
margin: 5px;
|
||||
border: 1px solid rgba(31, 56, 88, 0.2);
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 1px 4px 0 rgba(31, 56, 88, 0.15);
|
||||
|
||||
.tree-node-modal-title {
|
||||
position: relative;
|
||||
background: rgba(31, 56, 88, 0.04);
|
||||
padding: 0 10px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
border-bottom: 1px solid rgba(31, 56, 88, 0.2);
|
||||
|
||||
.tree-node-modal-title-visible-icon {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.tree-pane-modal-content {
|
||||
& > .tree-node-branches::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.tree-node-modal-radio, .tree-node-modal-radio-active {
|
||||
margin-right: 4px;
|
||||
opacity: 0.8;
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
left: 6px;
|
||||
}
|
||||
.tree-node-modal-radio-active {
|
||||
color: #006cff;
|
||||
}
|
||||
}
|
||||
|
||||
.tree-node-branches::before {
|
||||
position: absolute;
|
||||
display: block;
|
||||
|
||||
@ -9,13 +9,14 @@ import { intlNode } from '../locale';
|
||||
@observer
|
||||
export default class TreeBranches extends Component<{
|
||||
treeNode: TreeNode;
|
||||
isModal?: boolean;
|
||||
}> {
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
render() {
|
||||
const treeNode = this.props.treeNode;
|
||||
const { treeNode, isModal } = this.props;
|
||||
const { expanded } = treeNode;
|
||||
|
||||
if (!expanded) {
|
||||
@ -24,8 +25,10 @@ export default class TreeBranches extends Component<{
|
||||
|
||||
return (
|
||||
<div className="tree-node-branches">
|
||||
<TreeNodeSlots treeNode={treeNode} />
|
||||
<TreeNodeChildren treeNode={treeNode} />
|
||||
{
|
||||
!isModal && <TreeNodeSlots treeNode={treeNode}/>
|
||||
}
|
||||
<TreeNodeChildren treeNode={treeNode} isModal={isModal || false}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -34,12 +37,13 @@ export default class TreeBranches extends Component<{
|
||||
@observer
|
||||
class TreeNodeChildren extends Component<{
|
||||
treeNode: TreeNode;
|
||||
isModal?: boolean;
|
||||
}> {
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
render() {
|
||||
const { treeNode } = this.props;
|
||||
const { treeNode, isModal } = this.props;
|
||||
let children: any = [];
|
||||
let groupContents: any[] = [];
|
||||
let currentGrp: ExclusiveGroup;
|
||||
@ -67,6 +71,10 @@ class TreeNodeChildren extends Component<{
|
||||
/>
|
||||
);
|
||||
treeNode.children?.forEach((child, index) => {
|
||||
const childIsModal = child.node.getPrototype().isModal();
|
||||
if (isModal != childIsModal) {
|
||||
return;
|
||||
}
|
||||
const { conditionGroup } = child.node;
|
||||
if (conditionGroup !== currentGrp) {
|
||||
endGroup();
|
||||
@ -81,12 +89,12 @@ class TreeNodeChildren extends Component<{
|
||||
children.push(insertion);
|
||||
}
|
||||
}
|
||||
groupContents.push(<TreeNodeView key={child.id} treeNode={child} />);
|
||||
groupContents.push(<TreeNodeView key={child.id} treeNode={child} isModal={isModal}/>);
|
||||
} else {
|
||||
if (index === dropIndex) {
|
||||
children.push(insertion);
|
||||
}
|
||||
children.push(<TreeNodeView key={child.id} treeNode={child} />);
|
||||
children.push(<TreeNodeView key={child.id} treeNode={child} isModal={isModal}/>);
|
||||
}
|
||||
});
|
||||
endGroup();
|
||||
|
||||
@ -6,13 +6,16 @@ import TreeTitle from './tree-title';
|
||||
import TreeBranches from './tree-branches';
|
||||
|
||||
@observer
|
||||
export default class TreeNodeView extends Component<{ treeNode: TreeNode }> {
|
||||
export default class TreeNodeView extends Component<{
|
||||
treeNode: TreeNode;
|
||||
isModal?: boolean;
|
||||
}> {
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { treeNode } = this.props;
|
||||
const { treeNode, isModal } = this.props;
|
||||
const className = classNames('tree-node', {
|
||||
// 是否展开
|
||||
expanded: treeNode.expanded,
|
||||
@ -35,8 +38,8 @@ export default class TreeNodeView extends Component<{ treeNode: TreeNode }> {
|
||||
|
||||
return (
|
||||
<div className={className} data-id={treeNode.id}>
|
||||
<TreeTitle treeNode={treeNode} />
|
||||
<TreeBranches treeNode={treeNode} />
|
||||
<TreeTitle treeNode={treeNode} isModal={isModal}/>
|
||||
<TreeBranches treeNode={treeNode} isModal={false}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -10,11 +10,14 @@ import TreeNode from '../tree-node';
|
||||
import { IconEye } from '../icons/eye';
|
||||
import { IconCond } from '../icons/cond';
|
||||
import { IconLoop } from '../icons/loop';
|
||||
import { IconRadioActive } from '../icons/radio-active';
|
||||
import { IconRadio } from '../icons/radio';
|
||||
import { createIcon } from '@ali/lowcode-utils';
|
||||
|
||||
@observer
|
||||
export default class TreeTitle extends Component<{
|
||||
treeNode: TreeNode;
|
||||
isModal?: boolean;
|
||||
}> {
|
||||
state: {
|
||||
editing: boolean;
|
||||
@ -62,7 +65,7 @@ export default class TreeTitle extends Component<{
|
||||
};
|
||||
|
||||
render() {
|
||||
const { treeNode } = this.props;
|
||||
const { treeNode, isModal } = this.props;
|
||||
const { editing } = this.state;
|
||||
const isCNode = !treeNode.isRoot();
|
||||
const { node } = treeNode;
|
||||
@ -72,7 +75,7 @@ export default class TreeTitle extends Component<{
|
||||
const depth = treeNode.depth;
|
||||
const indent = depth * 12;
|
||||
style = {
|
||||
paddingLeft: indent,
|
||||
paddingLeft: indent + (isModal ? 12 : 0),
|
||||
marginLeft: -indent,
|
||||
};
|
||||
}
|
||||
@ -84,8 +87,31 @@ export default class TreeTitle extends Component<{
|
||||
})}
|
||||
style={style}
|
||||
data-id={treeNode.id}
|
||||
onClick={node.conditionGroup ? () => node.setConditionalVisible() : undefined}
|
||||
onClick={() => {
|
||||
if (isModal) {
|
||||
node.document.modalNodesManager.setVisible(node);
|
||||
return;
|
||||
}
|
||||
if (node.conditionGroup) {
|
||||
node.setConditionalVisible();
|
||||
return;
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isModal && node.getVisible() && (
|
||||
<div onClick={() => {
|
||||
node.document.modalNodesManager.setInvisible(node);
|
||||
}}>
|
||||
<IconRadioActive className="tree-node-modal-radio-active"/>
|
||||
</div>
|
||||
)}
|
||||
{isModal && !node.getVisible() && (
|
||||
<div onClick={() => {
|
||||
node.document.modalNodesManager.setVisible(node);
|
||||
}}>
|
||||
<IconRadio className="tree-node-modal-radio"/>
|
||||
</div>
|
||||
)}
|
||||
{isCNode && <ExpandBtn treeNode={treeNode} />}
|
||||
<div className="tree-node-icon">{createIcon(treeNode.icon)}</div>
|
||||
<div className="tree-node-title-label" onDoubleClick={isNodeParent ? this.enableEdit : undefined}>
|
||||
@ -123,7 +149,7 @@ export default class TreeTitle extends Component<{
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
{isCNode && isNodeParent && <HideBtn treeNode={treeNode} />}
|
||||
{isCNode && isNodeParent && !isModal && <HideBtn treeNode={treeNode} />}
|
||||
{/*isCNode && isNodeParent && <LockBtn treeNode={treeNode} />*/}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -3,7 +3,7 @@ import { observer, Editor, globalContext } from '@ali/lowcode-editor-core';
|
||||
import { isRootNode, Node, DragObjectType, isShaken } from '@ali/lowcode-designer';
|
||||
import { isFormEvent } from '@ali/lowcode-utils';
|
||||
import { Tree } from '../tree';
|
||||
import TreeNodeView from './tree-node';
|
||||
import RootTreeNodeView from './root-tree-node';
|
||||
|
||||
function getTreeNodeIdByEvent(e: ReactMouseEvent, stop: Element): null | string {
|
||||
let target: Element | null = e.target as Element;
|
||||
@ -155,7 +155,7 @@ export default class TreeView extends Component<{ tree: Tree }> {
|
||||
onClick={this.onClick}
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
>
|
||||
<TreeNodeView key={root.id} treeNode={root} />
|
||||
<RootTreeNodeView key={root.id} treeNode={root} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user