import { Component, MouseEvent as ReactMouseEvent } from 'react'; import { isFormEvent, canClickNode, isShaken } from '@alilc/lowcode-utils'; import { Tree } from '../controllers/tree'; import TreeNodeView from './tree-node'; import { IPublicEnumDragObjectType, IPublicModelPluginContext, IPublicModelNode } from '@alilc/lowcode-types'; function getTreeNodeIdByEvent(e: ReactMouseEvent, stop: Element): null | string { let target: Element | null = e.target as Element; if (!target || !stop.contains(target)) { return null; } target = target.closest('[data-id]'); if (!target || !stop.contains(target)) { return null; } return (target as HTMLDivElement).dataset.id || null; } export default class TreeView extends Component<{ tree: Tree; pluginContext: IPublicModelPluginContext; }> { private shell: HTMLDivElement | null = null; private hover(e: ReactMouseEvent) { const { project } = this.props.pluginContext; const detecting = project.currentDocument?.detecting; if (detecting?.enable) { return; } const node = this.getTreeNodeFromEvent(e)?.node; detecting?.capture(node as any); } private onClick = (e: ReactMouseEvent) => { if (this.ignoreUpSelected) { this.boostEvent = undefined; return; } if (this.boostEvent && isShaken(this.boostEvent, e.nativeEvent)) { this.boostEvent = undefined; return; } this.boostEvent = undefined; const treeNode = this.getTreeNodeFromEvent(e); if (!treeNode) { return; } const { node } = treeNode; if (!canClickNode(node, e)) { return; } const { project, event, canvas } = this.props.pluginContext; const doc = project.currentDocument; const selection = doc?.selection; const focusNode = doc?.focusNode; const { id } = node; const isMulti = e.metaKey || e.ctrlKey || e.shiftKey; canvas.activeTracker?.track(node); if (isMulti && !node.contains(focusNode) && selection.has(id)) { if (!isFormEvent(e.nativeEvent)) { selection.remove(id); } } else { selection.select(id); const selectedNode = selection?.getNodes()?.[0]; const npm = selectedNode?.componentMeta?.npm; const selected = [npm?.package, npm?.componentName].filter((item) => !!item).join('-') || selectedNode?.componentMeta?.componentName || ''; event.emit('outlinePane.select', { selected, }); } }; private onDoubleClick = (e: ReactMouseEvent) => { e.preventDefault(); const treeNode = this.getTreeNodeFromEvent(e); if (treeNode?.id === this.state.root?.id) { return; } if (!treeNode?.expanded) { this.props.tree.expandAllDecendants(treeNode); } else { this.props.tree.collapseAllDecendants(treeNode); } }; private onMouseOver = (e: ReactMouseEvent) => { this.hover(e); }; private getTreeNodeFromEvent(e: ReactMouseEvent) { if (!this.shell) { return; } const id = getTreeNodeIdByEvent(e, this.shell); if (!id) { return; } const { tree } = this.props; return tree.getTreeNodeById(id); } private ignoreUpSelected = false; private boostEvent?: MouseEvent; private onMouseDown = (e: ReactMouseEvent) => { if (isFormEvent(e.nativeEvent)) { return; } const treeNode = this.getTreeNodeFromEvent(e); if (!treeNode) { return; } const { node } = treeNode; if (!canClickNode(node, e)) { return; } const { project, canvas } = this.props.pluginContext; const selection = project.currentDocument?.selection; const focusNode = project.currentDocument?.focusNode; // TODO: shift selection const isMulti = e.metaKey || e.ctrlKey || e.shiftKey; const isLeftButton = e.button === 0; if (isLeftButton && !node.contains(focusNode)) { let nodes: IPublicModelNode[] = [node]; this.ignoreUpSelected = false; if (isMulti) { // multi select mode, directily add if (!selection.has(node.id)) { canvas.activeTracker?.track(node); selection.add(node.id); this.ignoreUpSelected = true; } // todo: remove rootNodes id selection.remove(focusNode.id); // 获得顶层 nodes nodes = selection.getTopNodes(); } else if (selection.has(node.id)) { nodes = selection.getTopNodes(); } this.boostEvent = e.nativeEvent; canvas.dragon?.boost( { type: IPublicEnumDragObjectType.Node, nodes, }, this.boostEvent, ); } }; private onMouseLeave = () => { const { pluginContext } = this.props; const { project } = pluginContext; const doc = project.currentDocument; doc?.detecting.leave(); }; state = { root: null, }; componentDidMount() { const { tree, pluginContext } = this.props; const { root } = tree; const { project } = pluginContext; this.setState({ root }); const doc = project.currentDocument; doc?.onFocusNodeChanged(() => { this.setState({ root: tree.root, }); }); } render() { if (!this.state.root) { return null; } return (