mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-21 10:07:07 +00:00
213 lines
5.6 KiB
TypeScript
213 lines
5.6 KiB
TypeScript
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 (
|
|
<div
|
|
className="lc-outline-tree"
|
|
ref={(shell) => { this.shell = shell; }}
|
|
onMouseDownCapture={this.onMouseDown}
|
|
onMouseOver={this.onMouseOver}
|
|
onClick={this.onClick}
|
|
onDoubleClick={this.onDoubleClick}
|
|
onMouseLeave={this.onMouseLeave}
|
|
>
|
|
<TreeNodeView
|
|
key={this.state.root?.id}
|
|
treeNode={this.state.root}
|
|
pluginContext={this.props.pluginContext}
|
|
isRootNode
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
}
|